<우아한 테크코스 5기 프리코스 진행사항 - 1주차>
0주차 'OT'를 통해 개요를 알 수 있었고 본격적으로 1주차 과제가 시작되었다.
1주차 과제는 여러 알고리즘 문제를 풀어내는 것이었다.
기능 요구 사항을 다양하게 제시하고, 제한사항 또한 주어지면 그것에 맞게 알고리즘을 구현해내면 되는 것이었다.
'프론트엔드 - Javascript / 백엔드 - Java 등' 언어로 과제를 수행하는 것이었고,
나는 백엔드 - Java로 과제를 수행할 수 있었다.
<Problem 1>
- 기능 요구 사항
- 포비와 크롱이 페이지 번호가 1부터 시작되는 400 페이지의 책을 주웠다.
- 제한 사항
- 구현과정
<Try 1>
// #3 : Result Max Value
int p_score = max;
// #3 : Result Max Value
int c_score = max;
// score system
if(p_score > c_score){
answer = 1;
}
else if(p_score < c_score){
answer = 2;
}
else if(p_score == c_score){
answer = 0;
}
else answer = -1;
#1 구현 부분에서 임의의 책을 펼치는 부분을 코드 자체적으로 만들어서 실행하려했다.
하지만 테스트케이스를 보니 pobi와 crong 리스트가 만들어질때 List.of로 지정해준다는 것이다.
따라서 #1구현부분은 불필요하게 되었다.
<Solution 1>
import java.util.List;
class Problem1 {
public static int solution(List<Integer> pobi, List<Integer> crong) {
int answer = Integer.MAX_VALUE;
int max = 0, sum = 0, mul = 1;
// #1 : List<Integer> xxx = List.of(97, 98);
// #2 : Find the Maximum (pobi)
for(int i = 0; i < 2; i++){
int pobi_num = pobi.get(i);
while(pobi_num > 0){
sum += pobi_num % 10;
mul *= pobi_num % 10;
pobi_num /= 10;
}
// #3 : Result Max Value
max = sum > mul ? sum : mul;
}
// #4 : Final Max score
int p_score = max;
// #2 : Find the Maximum (crong)
max = 0; sum = 0; mul = 1;
for(int i = 0; i < 2; i++){
int crong_num = crong.get(i);
while(crong_num > 0){
sum += crong_num % 10;
mul *= crong_num % 10;
crong_num /= 10;
}
// #3 : Result Max Value
max = sum > mul ? sum : mul;
}
// #4 : Final Max score
int c_score = max;
// #5 : Compare score (pobi, crong)
if(p_score > c_score){
answer = 1;
}
else if(p_score < c_score){
answer = 2;
}
else if(p_score == c_score){
answer = 0;
}
// #6 : Exception
else answer = -1;
return answer;
}
}
- #1~6까지 각각 테스트에 통과할 수 있는 조건문을 만들었는데, 날코딩이어서 그런지 예시 테스트케이스 2개는 통과할 수 있었지만 제출 후에는 테스트케이스를 통과할 수 없는 것들이 여럿 있었다.
Problem1 을 풀면서 느낀 것은 코드 리팩토링을 통해 테스트케이스를 적절히 통과할 수 있는 코드를 간결하게 잘 짜야한다는 것이었다. 코드를 길게 쓰면서 하나하나 테스트케이스에 맞춰서 작성하는 것이 아닌 요구사항에 맞게 작성해야한다는 것을 느낄 수 있었다.
<Problem 2>
- 기능 요구 사항
- 암호문을 좋아하는 괴짜 개발자 브라운이 이번에는 중복 문자를 이용한 새로운 암호를 만들었다.
- 제한 사항
- 구현과정
public class Problem2 {
public static String solution(String cryptogram) {
String answer = "";
// Exception
if (cryptogram == null || cryptogram.length() == 0) {
return answer;
}
// Progress_1
char[] compare = cryptogram.toCharArray();
char temp = 0;
int i = 0, k = 0;
// if) Symmetry(대칭) 구조일 경우 변수 선언
int lt = 0, rt = compare.length-1;
// if) Symmetry(대칭) 구조일 경우 예외 처리문
if(compare[lt] == compare[rt]) {
for(int j = 1; j < compare.length; j++) {
if(compare[lt + j - 1] == compare[rt - j + 1]) {
// 대칭 구조이면서 모든 중복이 제거되어 반환할 값이 없을 경우
if(compare[lt + j] == compare[rt - j]) return answer = "";
// 대칭 구조이면서 길이가 짝수일경우
if(cryptogram.length() % 2 == 0) {
return answer = cryptogram.substring(cryptogram.length()/2-1,
cryptogram.length()/2+1);
}
// 대칭 구조이면서 길이가 홀수일경우
else if(cryptogram.length() % 2 != 0) {
return answer = cryptogram.substring(cryptogram.length()/2,
cryptogram.length()/2+1);
}
continue;
}
}
}
// Progress_2
for(i = 1; i < compare.length; i++) {
if(compare[i - 1] != compare[i]) {
compare[k++] = compare[i - 1]; // 후위증가연산자, 이전 문자로 업데이트
}
else {
while (i < compare.length && compare[i - 1] == compare[i]) i++;
}
}
// 마지막 문자까지 추가
compare[k++] = compare[i - 1];
// Create New String
String c = new String(compare).substring(0, k);
// 중복이 제거되면 다시 시작(새롭게 구성된 문자열로 재호출) - 화가 알고리즘
if(k != compare.length) return solution(c);
answer = c;
// Remove All Overlap char result
return answer;
}
}
테스트케이스를 통과할 수 있도록만 코드를 구현해내는 과정에서 새롭게 알게된 알고리즘 방법이 있었다.
'재귀호출' 처럼 함수를 반복으로 호출하는 것인데 '화가 알고리즘' 이다.
다시말해 주어진 문자열에서 중복이 없을때까지 지속해서 인접한 중복을 제거해나가는 것이다,
lt와 rt변수를 통해 좌측에서 접근하는 인덱스, 우측에서 접근하는 인덱스를 구분하고자 하였고, .substring과 같은 메서드를 통해 새로운 문자열을 만들어낼 수 있었다.
<Problem 3>
- 기능 요구 사항
- 배달이가 좋아하는 369게임을 하고자 한다.
- 제한 사항
- 구현과정
public class Problem3 {
public static int solution(int number) {
// Count
int answer = 0;
for(int i = 1 ; i <= number; i++) {
if(i/10 == 3 || i/10 == 6 || i/10 == 9) answer++;
if(i%10 == 3 || i%10 == 6 || i%10 == 9) answer++;
}
return answer;
}
}
간단한 369 게임 로직이었다. 조건문 2줄 정도로 369 게임 처리를 할 수 있었다.
몫이 3,6,9일때와 나머지가 3,6,9일때 (=10의 자리가 3,6,9일때와 1의 자리가 3,6,9일때)
answer값을 증가시켜서 return 한다.
<Problem 4>
- 기능 요구 사항
- 어느 연못에 엄마 말씀을 좀처럼 듣지 않는 청개구리가 살고 있었다.
- 제한 사항
- 구현과정
public class Problem4 {
public static String solution(String word) {
String answer = "";
int temp;
char after;
for(char c : word.toCharArray()) {
if(c == ' ') answer += " ";
// UpperCase
if(Character.isUpperCase(c)) {
int diff = (char)c - 65;
temp = (char)90 - diff;
after = (char)temp;
answer += after;
}
// LowerCase
else if(Character.isLowerCase(c)) {
int diff = (char)c - 97;
temp = (char)122 - diff;
after = (char)temp;
answer += after;
}
}
return answer;
}
}
A~Z까지 순서대로 Z~A로 해석해서 반환하는 문제였다.
Character.isUpperCase로 대문자일 경우와 isLowerCase로 소문자일 경우를 나누어서 처리한다.
(대문자가 입력되면 청개구리 로직의 대문자로, 소문자가 입력되면 청개구리 로직의 소문자로)
변환은 아스키코드를 이용하였고, diff(차이 간격)을 지정한다.
65는 대문자 알파벳 A를 의미하는데, (char)c - 65로 입력된 문자 'c'에서 65를 빼면 A로부터 얼마만큼 떨어져있는지(diff)값이 나온다.
그 값을 대문자 알파벳 끝값인 90에서 빼면 A로부터 우측으로 diff만큼 떨어져있는 문자와
Z로부터 좌측으로 diff만큼 떨어져있는 문자를 구할 수 있다.
그 문자를 after라는 변수에 넣고 answer에 += 하여 누적시킨다.
소문자도 마찬가지로 구현해준다.
<Problem 5>
- 기능 요구 사항
- 계좌에 들어있는 돈 일부를 은행에서 출금하고자 한다.
- 제한 사항
- 구현과정
import java.util.ArrayList;
import java.util.List;
public class Problem5 {
public static List<Integer> solution(int money) {
List<Integer> answer = new ArrayList<Integer>();
int m, m1, m2, m3, m4, m5, m6, m7, m8, m9;
m1 = money / 50000;
money = money - m1 * 50000;
m2 = money / 10000;
money = money - m2 * 10000;
m3 = money / 5000;
money = money - m3 * 5000;
m4 = money / 1000;
money = money - m4 * 1000;
m5 = money / 500;
money = money - m5 * 500;
m6 = money / 100;
money = money - m6 * 100;
m7 = money / 50;
money = money - m7 * 50;
m8 = money / 10;
money = money - m8 * 10;
m9 = money / 1;
money = money - m9 * 1;
answer.add(0, m1);
answer.add(1, m2);
answer.add(2, m3);
answer.add(3, m4);
answer.add(4, m5);
answer.add(5, m6);
answer.add(6, m7);
answer.add(7, m8);
answer.add(8, m9);
return answer;
}
}
효율적인 코드로 구현하고 싶은 문제였다. 문제를 보면 매우 간단해보였지만
차감하는 부분이나 남은 금액을 업데이트해주는 부분이 조금 꼬일 것 같았다.
직관적으로 풀어본 결과 해결을 할 수 있었다. 하지만 효율적인 코드는 아닐 수도 있겠다 느꼈다.
m1부터 m9까지 50000원권~ 1원권으로 나누고
금액을 금액별로 나누어 몫을 저장하고 금액을 그만큼 차감해나가는 알고리즘이다.
answer는 ArrayList로 구현되어 .add(index, data)로 값을 넣어 마무리할 수 있었다.
<Problem 6>
- 기능 요구 사항
- 우아한테크코스에서는 교육생(이하 크루) 간 소통 시 닉네임을 사용한다.
- 제한 사항
- 구현과정
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Problem6 {
public static String[] solution(List<List<String>> forms) {
String[] answer = new String[forms.size()];
HashSet<String> emails = new HashSet<>();
final Map<String, String > hashMap = new HashMap<>();
for (int i = 0; i < forms.size(); i++) {
final String name = forms.get(i).get(1);
if(name.length() < 2){
break;
}
for (int j = 0; j < name.length() - 1; j++) {
final String key = name.substring(j, j+2);
if(hashMap.containsKey(key)){
final String email = hashMap.get(key);
if(!email.equals(forms.get(i).get(0))){
emails.add(email);
emails.add(forms.get(i).get(0));
}
}
hashMap.put(key, forms.get(i).get(0));
}
}
final List<String> collect = emails.stream()
.sorted()
.collect(Collectors.toList());
final String[] results = collect.toArray(new String[0]);
return results;
}
}
지금까지 푼 1주차 테크코스 문제 중에서 가장 어려운 문제였다.
전에도 비슷한 문제를 접한적이 있었다. 특정 유저에게서 신고를 받아 그 신고내역을 신고한 사람에게 전달하고,
신고당한 사람은 경고횟수를 늘려가는 프로그램이었다.
그런 문제와 비슷해보였는데, 두글자 이상 겹치는 닉네임을 가진 유저의 '이메일 목록'을 출력하는 문제였다.
이번 구현에서는 HashMap 과 HashSet이 쓰였는데, 중복을 제거할 수 있다는 장점때문이었다.
get(i)로 HashSet인 email 요소에 접근하고, forms에 들어있는 이메일과 닉네임 데이터를 처리하는 알고리즘이다.
이번 알고리즘에 쓰인 부분 중
1. final 선언자
final 선언자는 상수를 선언할때만 쓸 수 있다고 생각할 수 있지만 프로그램 내에서 변경되지 않을 정적인 변수를 만들어내는 선언자라고 할 수 있다. 위 코드에서도 final String key 처럼 key값은 변경되면 안되므로 선언해준 것을 확인할 수 있다.
2. List<List<String>> = 이중리스트
이중리스트, 즉 2차원 리스트를 선언할 수 있다는 것이었다. 여기서는 forms에 2차원 리스트가 적용되었는데,
유저리스트 안에 그 유저는 닉네임과 이메일을 리스트로 가지므로 2차원 리스트인 것이다.
3. HashSet<String> emails = new HashSet<>();
처럼 뒤에 new에서는 파라미터를 생략할 수 있다는 점
4. HashMap 특징
Map 인터페이스 종류로 Key-Value 값으로 묶어 데이터를 관리할 수 있는 클래스인데, 해싱기법으로 많은 양의 데이터 처리가 가능하다는 점이 장점이다.
따라서 파라미터가 두개가 필요하다. Key와 Value의 파라미터인 것이다.
HashMap을 생성할때 (Example : HashMap<Integer, String> TEST = new HashMap<>(); )
처럼 선언하는 것이다.
이러한 HashMap에 데이터를 넣을때는 List의 add와 달리 'put'을 사용한다.
Value값은 중복이 가능한 HashMap이지만 Key값은 중복을 허용하지 않으므로 만약 key값이 중복되면
나중에 선언된 key값 Value 데이터가 출력된다.
List에서는 인덱스로 접근할 수 있는 반면, HashMap에서는 key값으로 접근할 수 있다는 점이 차이점일 것이다.
5. HashMap의 containsKey
해석 그대로 Key값을 포함하는 결과를 반환한다.
if(hm.containsKey(key)) 처럼 조건문을 작성하면 key값에 해당하는 처리를 할 수 있다.
6. stream.sorted() 와 stream.collect(Collectors.toList())
1) sorted()는 스트림의 요소들이 특정 기준으로 정렬된 후 전달되는 새로운 스트림 객체를 리턴한다.
sorted를 활용할때
test.stream().sorted(Comparator.reverseOrder()) 처럼
Comparator 클래스의 .reverseOrder() 메서드를 사용하여 역순 정렬을 할 수도 있다.
2) collect()는 스트림의 데이터를 변형하는 처리를 하고 원하는 자료형으로 변환해주는 역할을 한다.
위 코드에서는 .collect(Collectors.toList())로 쓰였으므로 Collectors 클래스에 있는 .toList 메서드를 활용하여
리스트로 반환한다는 뜻인것이다.
7. final String[] results = collect.toArray(new String[0]);
결과값을 리턴하기 위해서 문자열배열로 선언된 results에 리스트로 선언된 collect를 배열로 바꿔주는 .toArray메서드를 통해 새롭게 만들어주는 것이다.
<Problem 7>
- 기능 요구 사항
- 레벨 2의 팀 프로젝트 미션으로 SNS(Social Networking Service)를 만들고자 하는 팀이 있다.
- 제한 사항
- 구현과정
import java.util.*;
public class Problem7 {
public static List<String> solution(String user, List<List<String>> friends, List<String> visitors) {
List<String> answer = new ArrayList<String>();
// #1
List<String> user_friends = new ArrayList<>();
// de_friends = Clone friends (중복 제거), Not friends, Enable Recommend
HashSet<String> de_friends = new HashSet<>();
for(List<String> s : friends) de_friends.addAll(s);
de_friends.remove(user);
String temp = "";
for(int i = 0; i < friends.size(); i++) {
// left side
if(user == friends.get(i).get(0)) {
temp = friends.get(i).get(1);
de_friends.remove(temp);
user_friends.add(temp);
}
// right side
else if(user == friends.get(i).get(1)) {
temp = friends.get(i).get(0);
de_friends.remove(temp);
user_friends.add(temp);
}
}
// #2
HashSet<String> all_user = new HashSet<>(); // All SNS Users
List<String> together = new ArrayList<>(); // 함께 아는 친구 목록
for(int i = 0; i < friends.size(); i++) {
for(int j = 0; j < 2; j++) {
temp = friends.get(i).get(j);
all_user.add(temp);
}
}
// Add Visitors
for(int i = 0; i < visitors.size(); i++) {
temp = visitors.get(i);
all_user.add(temp);
}
// #2-1 de_friends and user_friends
for(int i = 0; i < friends.size(); i++) {
for(String name : de_friends) {
if(name == friends.get(i).get(0)) {
temp = friends.get(i).get(0);
together.add(temp);
}
if(name == friends.get(i).get(1)) {
temp = friends.get(i).get(1);
together.add(temp);
}
}
}
// #3. together, score
// #3-1 Make score table
HashMap<String, Integer> score = new HashMap<String, Integer>();
String users;
Iterator iterator = all_user.iterator();
while (iterator.hasNext()) {
users = (String) iterator.next();
score.put(users, 0);
}
int sum;
for(int i = 0; i < together.size(); i++) {
for(String t : de_friends) {
if(together.get(i) == t) {
sum = score.get(t);
sum += 10;
score.put(t, sum);
}
}
}
// #4. Visitors score
for(String v : visitors) {
if(score.containsKey(v)) {
sum = score.get(v);
sum++;
score.put(v, sum);
}
}
// #4-1 user or friends >> Remove score list
if(score.containsKey(user)) score.remove(user);
for(int i = 0; i < user_friends.size(); i++) {
temp = user_friends.get(i);
if(score.containsKey(temp)) score.remove(temp);
}
// #5. 점수별로 정렬 (sort)
List<Map.Entry<String, Integer>> results = new ArrayList<Map.Entry<String, Integer>>(score.entrySet());
Collections.sort(results, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> obj1, Map.Entry<String, Integer> obj2)
{
// 내림 차순 정렬
return obj2.getValue().compareTo(obj1.getValue());
}
});
for(Map.Entry<String, Integer> entry : results) {
if(entry.getValue() > 0 ){
answer.add(entry.getKey());
}
}
return answer;
}
}
이번에는 간단한 SNS 프로그램을 만드는 것이다.
모든 기능을 구현하는 것이 아닌 방문자와 방문횟수 데이터를 처리하는 알고리즘이었다.
HashSet의 addAll 기능으로 모든 요소를 복사할 수 있다. 만약
List의 요소를 HashSet에 복사하고 싶을때도 List로 Collections의 한 종류이므로 addAll을 사용할 수 있다.
또한 Map.Entry 는 Map 형태의 인터페이스를 만드는데 사용한다. 예를 들어 Map을 for문 처리할때나
인터페이스 용도로 사용할때, 스트림(Stream)을 사용 시, Map 형식의 데이터에서 처리가 필요할때 사용한다.
따라서 getKey메서드는 Map.Entry의 메서드를 사용하는 것이다.
getValue또한 Map의 파라미터인 Value를 가져오는 메서드이다.
#1에서 de_friends라는 변수로 기존 friends 2차원 리스트에서 중복을 제거한 후 friends를 복사한 리스트를 만드는 것이 구현 목표였다.
#2에서는 모든 유저 목록인 HashSet<String> all_user와 함께 아는 친구 목록인 List<String> together를 선언하여 friends리스트로부터 부분 데이터를 가져와 처리하는 것이 목표였다.
get(i).get(0) 이중 get메서드를 통해 2차원 적인 데이터에 접근하는 것을 알 수 있었다.
구현을 하면서 어려웠던 점이 #3에서 Iterator의 사용이었다.
Iterator는 컬렉션 프레임워크(List, Set, Map, Queue)에서 값을 가져오거나 삭제할때 사용하는데,
사용을 하는 이유로 모든 컬렉션 프레임워크에 공통으로 사용가능하다는 점과 컬렉션 프레임워크에서 쉽게 값을 가져오고 제거할 수 있다는 점이다.
광범위한 데이터를 가져올 수 있다는 점이 장점이지만 그에 반해 단방향 반복만 가능하다는 점과 값변경, 추가가 불가능 하다는 점, 속도가 느리다는 점을 단점으로 말할 수 있다.
하지만 테크코스 7번문제를 구현하면서 Iterator의 .hasNext()메서드 .Next 메서드 등을 통해 코드 사용에 큰 도움이 되었다.
구현에 어려움을 겪고 있을때 Iterator사용을 통해 해결할 수 있었던 것이다.
.compareTo 라는 메서드를 통해 비교값을 리턴해낼 수 있었다.
compareTo 메서드는 두개의 값을 비교하여 int값으로 반환해주는 함수인데, 문자열 비교도 가능하다.
문자열 비교또한 숫자로 반환하는데, Example로
String str = "abcde";
abcde 에서 ab가 포함되어있으면 >> str.compareTo("ab") >> 2를 리턴한다.
기준값에 비교대상이 포함되어있을 경우 서로의 문자열 길이 차이값을 리턴해준다.
제출 결과로 12개의 테스트케이스 중 10개만 통과하였다. 모두 통과하지 못한점이 아쉽지만, 처음 경험해보면서 배운점이 매우 많았기때문에 뜻깊은 1주차 과제수행이었다.
다음부터는 코드 리팩토링으로 더 효율적인 코드를 적는 법과 다양한 자바 클래스를 활용하여 해결하는 방법을 미리 공부해봐야겠다는 생각을 하였다.
'Recording > 우아한테크코스 5기 Pre-course' 카테고리의 다른 글
[마무리🎆] [우테코 5기] <4주차> 'java-bridge' 회고 (0) | 2023.03.11 |
---|---|
[우테코 5기] <3주차> 'java-lotto' 회고 (0) | 2022.12.14 |
[우테코 5기] <2주차> 'java-baseball' 회고 (1) | 2022.12.13 |
[우테코 5기] <0주차> OT 후기 (0) | 2022.11.06 |