지원할까 말까부터 중도 포기가 맞나 한참 고민하던 1주 차가 드디어 끝났습니다. (회고록은 높임말루 ㅋ)
웹 개발자가 되고 싶어서 스프링을 시작했는데.. 막상 코테는 또 파이썬으로 준비하니 자바라는 언어라는 것만 알지
실제로 자바에 어떤 기능이 있는지는 잘 몰라 한번 도전해봤습니다. (업보 stack 쌓이는중..)
쨋든 그래도 1주차를 잘 마무리한 김에.. 내일 과제가 새로 나오니 그전에 한번 정리해보고자 합니다.
사설은 여기까지!
Problem1
책펴기 게임으로 책 페이지 숫자 중(오른쪽, 왼쪽) 각각 각 자리의 곱과 덧셈 중 큰 값을 점수로 가지는데
이때 더 큰 값을 가지는 사람이 이기는 게임입니다.
public static int maxSumOrMultiply(int num){
int[] digit= Stream.of(String.valueOf(num).split(""))
.mapToInt(Integer::parseInt)
.toArray();
int sum= Arrays.stream(digit).sum();
int mul= Arrays.stream(digit).reduce(1,(a,b)->a*b);
return Math.max(sum,mul);
}
해당 매소드를 만들어 더 큰 값을 반환받도록 하였습니다.
자바는 for문 대신 stream을 많이 쓴다는 점을 처음 알게 되었습니다.
stream에는 기본적인 내장 함수 sum, max, min.. 등등이 있어 로직을 구현하지 않아도 쉽게 값을 도출해낼 수 있고,
reduce함수를 통해 람다식으로 곱을 만들어 도출해낼 수 있다는 것을 알게 되었습니다.
Problem2
문자열이 주어지면 2개 이상 연속으로 중복되는 문자를 끝까지 삭제하여 암호를 해독하는 문제입니다.
while(cryptogram.length()>0){
//변수 선언부
for(int i=1 ; i<cryptogram.length();i++){
char c = cryptogram.charAt(i);
if(Objects.equals(c,prev)) lenSamePrev+=1;
else if (lenSamePrev>1) lenSamePrev=1;
else newCrypt.append(prev);
prev=c;
}
if (lenSamePrev==1)newCrypt.append(prev);
if (Objects.equals(newCrypt.toString(),cryptogram)) break;
cryptogram= newCrypt.toString();
}
자바의 기능을 이용해 이것보다 훨씬 깔끔하게 짜신 분들도 많지만,
그냥 단순한 로직을 이용하였습니다.
이전 문자를 char형에 저장해주고 연속되는 길이를 int로 선언하여, 문자가 반복되었을 경우에는 new에 저장하지 않아 연속으로 중복되는 문자는 저장되지 않도록 하였습니다.
자바의 String은 값을 더하거나 뺄 때 해당 변수를 변경하는 것이 아닌 더해진 값을 새로 생성하여 해당 변수에 넣어준다고 하여
StringBuilder을 사용해 메모리 낭비를 줄였습니다.
또한 ==연산은 주소 값이 같은지의 연산이지 두 변수가 같은 값을 가지는지는 알 수 없어 equals를 사용하였습니다.
Problem3
특정 범위까지의 수에서 3,6,9가 몇 번 나오는지 세서 반환하는 문제입니다.
return (int) strNum.chars()
.filter(n-> n=='3' || n=='6' || n=='9')
.count();
파이썬과 달리 자바에는 Counter이 없어 filter을 사용하여 개수를 세주었습니다.
또한 filter에 or 옵션을 넣어 한번에 셀 수 있게 해 주었습니다.
Problem4
주어진 문장을 a->z, b->y... 이렇게 대칭으로 변경하여 반환해주어야 하는 문제입니다.
public static String changeAlpha (String word){
char[] chars=word.toCharArray();
int upperSymmetrySum=(int)'A'+(int)'Z';
int lowerSymmetrySum=(int)'a'+(int)'z';
for(int i=0; i<chars.length; i++){
if(!isAlphabetic(chars[i]))continue;
if(Character.isUpperCase(chars[i]))chars[i]=symmetryChar(upperSymmetrySum,chars[i]);
else chars[i]=symmetryChar(lowerSymmetrySum,chars[i]);
}
return String.valueOf(chars);
}
public static char symmetryChar(int symmetrySum, char c){
return (char)(symmetrySum-(int)c);
}
자바는 casting으로 쉽게 아스키코드 값을 구할 수 있습니다. char형을 int형으로 캐스팅하면 바로 아스키코드값이 반환됩니다.
문제에서 알파벳만 전환하라고 하였으므로 isAlphabetic으로 확인 후 대문자, 소문자를 확인하여 변환하여주었습니다.
Problem5
돈의 종류가 50000, 10000, 5000, 1000, 500, 100, 50, 10, 1으로 주어지고 돈의 액수가 주어질 때
지갑을 가장 가볍게 다니는 방법을 구하는 문제입니다.(종류별 무게 x)
다시 말해, 가장 단순한 그리디 문제라고 볼 수 있습니다.
미리 moneyType과 pocket을 선언 후 pocket은 0을 집어넣어 주었습니다.
Math.floorDiv 와 Math.floorMod를 통해 각각 몫과 나머지를 넣어주었고 주어진 액수가 0이 되면 멈추게 해 줬습니다.
여기서 왜 일반적인 / 과 %이 아닌 Math의 함수를 이용했냐?라고 물으시면
해당 함수가 더 정확한 결괏값을 내준다고 하여 (특히 음수일 때) 미리 익혀둘 겸 Math의 함수를 이용하였습니다.
return Arrays.asList(
Arrays.stream(pocket)
.boxed()
.toArray(Integer[]::new)
);
또한 쉽게 구현하기 위해 int [] 즉, int(자료형) 배열을 선언하였는데 return 값은 Integer(클래스)이라서 변환이 필요했습니다.
그리하여 stream을 통해 배열을 각각 접근해주고 boxed를 통해 Integer값으로 바꿔주었습니다.
※Integer은 unboxing을 통해 연산이 가능함.
Problem6
크루 중 두 글자 이상 연속의 이름이 중복될 경우 이메일을 담아 배열에 넣어 반환하는 문제입니다.
for(int i=0; i<forms.size(); i++){
for (int j=i+1; j<forms.size() j++){
String subCrewName=crewName(i,forms).substring(idx,idx+leastLen);
if(crewName(j,forms).contains(subCrewName)){
set.add(crewEmail(i,forms));
set.add(crewEmail(j,forms));
}
}
}
한참 로직을 고민하던 중 그냥 부르트포스가 제일 적합할 거 같아 부르트포스로 풀었습니다.
중복일 필요가 없어 set 자료형으로 선언한 후
부르트포스를 통해 i번째 크루와 i보다 뒤에 있는 인덱스를 가지는 j를 비교해주는데
이때 i를 2 글자씩 substring 하여 j에 속해있는지 확인해주고 맞다면 set에 넣어주었습니다.
set에 들어간 email값 들을 sorting 하여 반환해주었습니다.
-> 한번에 정렬하여 반환하였는데 @email.com을 떼어내고 소팅했어야 숫자와 길이까지 고려한 소팅이 되었을 것 같음.
Problem7
친구 추천 로직을 만드는 문제입니다.
서로 아는 친구가 있을 경우에는 10점, user의 sns에 방문했을 때는 1점을 부여합니다.
해당 문제가 가장 어려웠습니다.
파이썬으로 코테를 준비하니 단순한 게 익숙해져서 되게 당연히 돼야 할 거 같던 부분이 안되니까 많이 삽질했던 것 같습니다.
- 전역 변수
: 함수를 쪼개다 보니 전역변수로 생성하여 쉽게 데이터를 저장하는 게 낫다고 판단해 전역 변수로 선언
static Map<String,Long> scores = new HashMap<>();
static Map<String,String[]> map = new HashMap<>();
- 친구점수 함수
: 일단 서로 친구가 누구인지 저장해주기 위해 이름을 key로 가지고 친구 이름 리스트를 value로 가지는 map을 선언
친구의 친구가 10점을 얻는 로직이므로 반복문을 통해 친구의 친구를 찾아냄
public static void friendScore(String user, List<List<String>> friends){
for(List<String>fs:friends){
insertMap(fs.get(0),fs.get(1));
insertMap(fs.get(1),fs.get(0));
}
for(String userF: map.getOrDefault(user,new String[0])){
String[] nearF = map.getOrDefault(userF,new String[0]);
for(String name:nearF){
insertScores(user,name,10L);
}
}
}
-친구 매핑 맵 추가 함수
: 선언된 map에 추가
public static void insertMap(String key, String value){
List<String> list = new ArrayList<>(Arrays.asList(map.getOrDefault(key,new String[0])));
list.add(value);
map.put(key, list.toArray(String[]::new));
}
- 방문자 점수 함수
: 방문을 한 친구가 아닌 유저에게 방문할 때마다 1점씩 부과해야 하므로 counter을 생성하여 방문횟수를 점수로 부과
public static void visitorScore(String user, List<String> visitors){
Stream<String> stream = visitors.parallelStream();
Map<String,Long> counter = stream.collect(
Collectors.toConcurrentMap(k->k,v->1L,Long::sum)
);
for (String name: counter.keySet()){
insertScores(user,name,counter.get(name));
}
}
- 점수 입력 함수
: friendscore / visitscore의 양식이 같아 점수 입력 함수 생성
1. 전달받은 name과 user이 같으면 return, 본인은 본인과 친구가 될 수 없음
2. 이미 유저의 친구일 경우 return
3. 위의 사항에 충족하지 않을 경우 데이터 추가
public static void insertScores(String user, String name, Long score){
if (Objects.equals(user,name)) return;
List<String>userFriends= List.of(map.getOrDefault(user,new String[0]));
if (userFriends.contains(name)) return;
Long existScore=scores.getOrDefault(name,0L);
scores.put(name,existScore+score);
}
- 소팅함수 & 리턴 함수
: scores에 저장된 데이터를 리스트로 변환하여 1. 점수 2. 이름 순으로 소팅해줌
: 최대 5개까지 반환해 주면 되므로 리턴 함수에서 잘라서 반환
public static List<String> sortValue(){
List<Object[]> scoreList=new ArrayList<>();
for (String key :scores.keySet()){
scoreList.add(new Object[]{key, scores.get(key)});
}
scoreList.sort(
Comparator.comparingLong(
(Object[] o)->(Long)o[1])
.reversed()
.thenComparing(
(Object[] o)->(o[0].toString())
)
);
return returnValue(scoreList);
}
public static List<String> returnValue(List<Object[]> listObjects){
List<String> ans = new ArrayList<>();
int maxValueNum=5;
if(listObjects.size()>maxValueNum)listObjects=listObjects.subList(0,maxValueNum);
for(Object[] l : listObjects){
ans.add(l[0].toString());
}
return ans;
}
소팅 함수를 만들다가 Object에 대한 개념이 좀 잡힌 것 같습니다.
리스트에 String과 int를 모두 넣어주어야 하여 더 큰 개념인 Object로 리스트를 선언해준 뒤
scores에 있는 데이터를 넣어주고 Comparator로 소팅을 해주는데,
이때 Object에 String이나 int가 들어간다고 해서 알아서 캐스팅해주지 못하므로 비교해줄 때 캐스팅을 하여 넘겨주었습니다.
람다 식을 사용하여 넘겨주니 훨씬 깔끔한 코드로 정리되었습니다.
소감
온보딩을 통해 자바의 특성을 좀 더 알게 된 것 같습니다.
이런 문제들은 파이썬으로만 구현하다 보니 로직은 머릿속으로 다 그려지는데 실제 구현이 제대로 안돼서 애를 많이 먹었습니다.
구글링의 연속이었달까.... 원래 그렇긴 하지만....
7번 문제는 헛짓거리를 하도 해서 풀코드로 정리하였지만,
나머지는 좀 기억했으면 하는 내용을 중심으로 적어보았습니다.
요구사항을 직접 막 고민한 게 아니라서 얻어간 게 자바 특성뿐이라는 게 이번 과제의 아쉬움입니다.
다음 과제는 요구사항부터 설계하여 구현에만 목매지 않아 보려고 합니다.
또한 클린코드를 위해 기능단위로의 분해, 정확한 명명을 적극적으로 적용해보고자 합니다.
자꾸 업보만 쌓아가는 거 같은데 그래도 뭐든 하나씩 끝내면 달라지는 점이 있겠지?라는 믿음으로 이번 주도 열시뮈.. 해보고
2주 차 회고록으로 돌아올 수 있도록 노력하겠습니다아-
끝!
'📔 회고' 카테고리의 다른 글
[우테코 5기] 프리코스 2주차 야구게임 코드 리뷰 및 회고록 (0) | 2022.12.01 |
---|