우아한테크코스 5기 - 프리코스 1주차 미션 [온보딩]

core·2022년 11월 2일
0

우아한테크코스

목록 보기
2/6

서론

우아한테크코스 5기의 1주차 미션은 7개의 알고리즘 문제풀이였다.

본격적인 과제 들어가기에 앞서 준비운동을 위한 과제라고 생각이 되었다.
오리엔테이션 때 난이도는 쉬운 문제라고 하였으나 알고리즘 문제를 전혀 풀어본 경험이 없는 사람들에게는 만만치 않겠다는 생각이 들었다.

이번 과제를 통해서 각각의 문제마다 내가 생각했었던 문제풀이 설계 과정을 여기에 적어보려고 한다. 내 생각이 무조건 옳다라는 보장은 없다. 코드는 내용상 길어지므로 첨부는 하지 않았다.

기능 구현 유의사항

문제 풀기에 앞서 다음과 같은 진행 방식을 유의하면서 기능을 구현해야 한다.

  • 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
  • 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
  • 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.

🚀 기능 요구 사항

기능 요구 사항은 7가지의 알고리즘 문제를 말한다. 이 7문제에 대해서 요구 사항에 따라 적절한 기능을 구현하면 된다.

🎯 프로그램 요구 사항

  • JDK 11 버전에서 실행 가능해야 한다. JDK 11에서 정상적으로 동작하지 않을 경우 0점 처리한다.
  • build.gradle을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다.
  • 프로그램 종료 시 System.exit()를 호출하지 않는다.
  • 프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다.
  • 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.

✏️ 과제 진행 요구 사항

  • 미션은 java-onboarding 저장소를 Fork & Clone해 시작한다.
  • 과제 진행 및 제출 방법은 프리코스 과제 제출 문서를 참고한다.

🚀 Problem 1

🔔 기능 목록 정리

  • 페이지 번호 게임을 하기 위해서 페이지 번호 검증 기능
    • pobi와 crong에서 들어온 페이지가 1페이지 이상 400페이지 이하인지 확인
    • 리스트 형태인 pobi와 crong에서 첫 요소의 값은 홀수이고, 두 번째 요소의 값은 짝수인지 확인
    • pobi, crong 둘 다 오른쪽 페이지가 왼쪽 페이지보다 값이 커야 하고, 둘의 차이가 1인지 확인
  • 페이지 번호 규칙에 따라 한 페이지에서의 페이지 번호 값을 추출기능
  • 페이지 번호 검증, 게임 진행 후 pobi와 crong의 페이지 번호 게임의 결과값의 크기 비교에 따라 적당한 값 반환

🤔 고찰의 단계 과정

  1. 페이지 번호 게임을 하기에 앞서서 pobi 또는 crong이 가진 페이지 번호들이 페이지 게임을 하기에 적절한지 검증하는 과정을 먼저 거쳐야 한다는 생각이 들었다. 내가 생각한 검증의 내용은 다음과 같다.
    • pobi 또는 crong이 가지고 있는 페이지 정보들이 전부 1페이지 이상 400페이지 이하인지 검증
    • pobi 또는 crong이 가지고 있는 페이지 정보에서 왼쪽 페이지는 홀수, 오른쪽 페이지는 짝수이므로 pobi또는 crong이 가지고 있는 페이지 정보 중 첫 번째 인덱스는 홀수, 두 번째 인덱스는 짝수인지 검증
    • 오른쪽 페이지와 왼쪽 페이지의 차이가 1인지 검증
  2. 페이지 번호 게임이 가능하지 않은 경우라면 -1을 반환한다.
  3. 페이지 번호 게임이 가능한 경우라면 페이지 번호 게임에 따라서 pobi와 crong의 페이지 번호 게임 결과값을 추출한다.
  4. 만약 pobi의 페이지 번호 게임 결과가 crong의 페이지 번호 게임의 결과보다 크다면 1을 반환한다.
  5. 만약 pobi의 페이지 번호 게임 결과가 crong의 페이지 번호 게임의 결과보다 작다면 2을 반환한다.
  6. 만약 pobi의 페이지 번호 게임 결과가 crong의 페이지 번호 게임의 결과와 같다면 0을 반환한다.

🚀 Problem 2

🔔 기능 목록 정리

  • 연속된 문자가 나오는 문자열의 밤위 찾기
  • 중복된 문자로 구성된 문자열을 제거

🤔 고찰 단계 과정

  1. 문자열의 시작점부터 살펴보면서 중복된 문자가 나오는 지점을 찾는다. 중복된 문자가 나오는 지점을 중복 시작점으로 잡아준다.
  2. 중복 문자열의 시작점부터 중복 문자가 어디까지 연속되었는지 그 끝지점을 잡아준다. 반복문을 통해서 중복 끝지점을 잡아주었다.
  3. 중복 시작점과 중복 끝지점을 잡아주었으므로 입력으로 들어온 기존 문자열에서 이 범위의 문자열을 제거한다.
    • 중복된 문자로 구성된 문자열이 기존 문자열의 중간 부분에 있으면 중간 부분의 문자열을 지운 후 나뉘어진 두 문자열을 서로 합쳐준다.
  4. 1번 ~ 3번의 과정을 반복한다.
  5. 1번의 과정에서 중복 시작점을 잡지 못했다면 문자열에서 중복되는 문자가 없다는 의미이므로 현재 문자열을 반환한다.

🚀 Problem 3

🔔 기능 목록 정리

  • 1부터 주어진 자연수까지 3, 6, 9의 출현 횟수 계산

🤔 고찰 단계 과정

  • 단순 반복문을 활용하여 1부터 주어진 자연수까지 3, 6, 9의 출현 횟수를 계산해주었다.

🚀 Problem 4

🔔 기능 목록 정리

  • 주어진 문자열에서 특정 알파벳의 대소문자 구분 기능
  • 대문자에 대응하는 청개구리 문자 추출 기능
  • 소문자에 대응하는 청개구리 문자 추출 기능

🤔 고찰 단계 과정

  1. 문제 그림에서 사전순으로 정렬된 대문자의 배열에 대응하는 청개구리 문자의 정보를 관찰하였다.
  2. 이 정보를 보고 사전순 문자로 구성된 대문자의 문자열 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"과 사전순 문자로 구성된 소문자의 문자열 "abcdefghijklmnopqrstuvwxyz"을 만들었다.
  3. 청개구리 결과 문자열을 담아주기 위해서 자바의 StringBuilder 객체를 사용하였다.
    • StringBuilder 객체는 다음과 같이 선언이 가능하다.
      StringBUilder sb = new StringBuilder();
    • StringBuilder의 append 메서드를 통해서 문자 또는 문자열을 추가할 수 있다.
      sb.append('c')
    • StringBuilder 객체로 들어온 문자 또는 문자열은 배열의 형태로 문자를 저장하기 때문에 toString 메서드를 통해서 문자열로 변형을 해준다.
      sb.toString()
  4. 주어진 word 문자열을 구성된 문자를 하나씩 살피면서 특정 문자가 소문자인데 대문자인지 구별하였다.
  5. 만약 대문자이면 대문자에 대응하는 청개구리 문자를 StringBuilder 객체에 담는다.
    • 대문자에 대응하는 청개구리 문자를 찾는 방법은 다음과 같다.
      • 예를 들어 어떤 문자 A를 살펴본다고 한다면 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"에서 0번 인덱스를 말한다.
      • 사전순 대문자열의 전체 크기는 26이므로 청개구리 문자는 25번 인덱스에 대응한다.
      • 즉 청개구리 문자의 인덱스는 다음과 같이 뽑을 수가 있다.
        25 - 대문자열에서 특정 문자의 인덱스 번호
  6. 만약 소문자이면 소문자에 대응하는 청개구리 문자를 StringBuilder 객체에 담는다.
  7. 소문자, 대문자도 아니면 그냥 StringBuilder 객체에 담는다.
  8. StringBuilder에 담겨진 문자의 배열을 toString 메서드를 통해서 문자열로 변형해준다.

🚀 Problem 5

🔔 기능 목록 정리

  • 주어진 돈에 대하여 필요한 특정 돈(50000원 지폐, 10000원 지폐 등)의 개수 구하기

🤔 고찰 단계 과정

  1. 문제에서 필요한 돈의 개수를 최소로 해야 한다.
  2. 그래서 먼저 주어진 돈에 대해서 가장 큰 값을 가진 돈(50000)부터 필요한 개수를 구하는 방식을 고안하였다.
  3. 이를 위해서 내림차순으로 구성된 돈 정보의 배열을 만들었다.
    moneyList = [50000, 10000, 5000, 1000, 500, 100, 50, 10, 1]
  4. moneyList의 첫 원소보터 주어진 돈을 나눈 몫의 정보를 담아준다. 이 정보가 곧 각 돈 당 필요한 개수이다.
  5. 나눈 몫을 구했으면 주어진 돈을 나눈 나머지 값으로 업데이트 한다.

🚀 Problem 6

🔔 기능 목록 정리

  • 주어진 닉네임의 목록에서 특정 닉네임에 대한 연속으로 중복된 다른 닉네임 존재 여부 판단 기능
  • 중복된 닉네임이 존재한 이메일들 사전순으로 정렬

🤔 고찰 단계 과정

  1. 주어진 닉네임들에 대해서 어떤 닉네임이 연속으로 중복된 다른 닉네임이 존재하는지 판단하는 기능을 먼저 고찰했다.
    • 연속 중복된 닉네임의 존재여부를 판단을 위해서 boolean형의 배열을 사용하였다. 배열 이름은 visited로 설정
      boolean[] visited = new boolean[forms.size()];
    • 예를 들어서 닉네임 "제이엠", "제이슨", "워니"가 있다면 "제이엠"과 "제이슨"에서 제이가 중복이 되므로 visited는 정보는 다음과 같다.
      [true, true, false]
  2. 닉네임 목록을 살피면서 연속 중복된 닉네임 검증을 위해서 내 입장에서 단순히 떠오르는 방식은 이중 반복문을 통한 확인 방법이었다.
    • 두 닉네임을 검사할 때 연속된 두 글자가 같기만 해도 두 닉네임은 중복된 글자가 있는 닉네임이라고 판단을 하였다.
  3. 각 닉네임에 대하여 visited를 업데이트 한다.
  4. visited의 정보를 바탕으로 중복된 닉네임끼리 이메일을 따로 리스트에 담아준다.
  5. 담겨진 이메일들에 대해 사전순 정렬한다.

🚀 Problem 7

🔔 기능 목록 정리

  • 사용자의 친구 목록 담기 기능
  • 사용자를 제외한 모든 사람들에 대한 점수 0점 부여 기능
  • 함께 아는 친구에 대하여 점수 10점 부여 기능
  • 방문자에 대하여 점수 1점 부여 기능
  • 결과 정렬 기능

🤔 고찰 단계 과정

  1. 사용자와 함께 아는 친구의 의미가 무엇인지 처음에는 잘 이해가 가지 않았다.
  2. 거듭 고민을 해본 결과 나는 사용자와 함께 아는 친구에 대하여 다음과 같이 생각하였다.
    • 사용자의 직접적인 친구 관계는 아무 점수도 부여하지 않는다. (사용자 - 친구)
    • 사용자의 친구의 친구 관계는 점수 10점을 부여한다. (사용자 - 친구 - 친구)
  3. 사용자의 직접적인 친구는 0점을 부여해야하므로 사용자의 친구 목록 리스트에 사용자의 친구를 담았다.
  4. 사용자를 제외한 모든 사람들에 대하여 추천 점수를 먼저 0점으로 초기화를 진행했다. 이 때 사용한 객체는 자바의 HashMap을 사용하였다.
    • 선언 및 점수 부여 방식은 다음과 같이 진행했다.
      선언: Map<String, Integer> map = new HashMap<>();
      점수 부여: map.put("name", 0);
    • 여기서 사용자 자신은 점수를 부여할 필요가 없으므로 HashMap 목록에서 제외한다.
  5. friends에서 2번에서 고찰한 결과를 바탕으로 HashMap을 통해서 10점을 부여하였다.
  6. visitors에서도 마찬가지로 HashMap을 통해서 2번에 따라 점수 1점을 부여한다.
  7. HashMap에 있는 사람들마다 점수 부여를 완료했으므로 점수 기준으로 내림차순으로 HashMap을 정렬한다.
    • HashMap의 내림차순 방식은 다음과 같다.
    List<Map.Entry<String, Integer>> entryList = new LinkedList<>(map.entrySet());
     entryList.sort(new Comparator<Map.Entry<String, Integer>>() {
         @Override
         public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
             return o2.getValue() - o1.getValue();
         }
    • 위의 방식의 코드를 줄이면 다음과 같다.
    List<Map.Entry<String, Integer>> entryList = new LinkedList<>(map.entrySet());
     entryList.sort((o1, o2) -> o2.getValue() - o1.getValue());
  8. 내림차순으로 정렬된 점수를 기준으로 HashMap에서 최대 5개의 이름들을 뽑아 따로 리스트로 모아준다. (단, 0점인 지점부터는 뽑지 않는다.)
  9. 중복된 점수 구간에 대해서 해당 구간에 속한 이름들을 사전순으로 정렬해준다.

1주차 과제 후 받은 공통 피드백

🔔 요구사항을 정확히 준수한다

과제 제출 전에 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항의 항목을 모두 잘 지켰는지 다시 한 번 점검한다.

🔔 커밋 메시지를 의미 있게 작성한다

커밋 메시지에 해당 커밋에서 작업한 내용에 대한 이해가 가능하도록 작성한다.

🔔 git을 통해 관리할 자원에 대해서도 고려한다

.class 파일은 java 코드가 있으면 생성할 수 있다. 따라서 .class 파일은 굳이 git을 통해 관리하지 않아도 된다.
IntelliJ IDEA의 .idea 폴더, Eclipse의 .metadata 폴더 또한 개발 도구가 자동으로 생성하는 폴더이기 때문에 굳이 git으로 관리하지 않아도 된다.
앞으로 git에 코드를 추가할 때는 git을 통해 관리할 필요가 있는지를 고려해볼 것을 추천한다.

🔔 Pull Request를 보내기 전 브랜치를 확인한다

기능 구현 작업을 fork된 Repository의 main branch가 아닌, 기능 구현을 위해 새로 만든 브랜치에서 작업한 후 PR을 보낸다.

🔔 PR을 한 번 작성했다면 닫지 말고 추가 커밋을 한다

PR을 이미 한 번 보냈다면, 새로운 PR을 생성할 필요가 없다. 수정이 필요하다면 추가 커밋을 하면 자동으로 반영된다. 단, 미션 제출 기간 이후에는 추가 커밋을 하지 않는다.

🔔 이름을 통해 의도를 드러낸다

나 자신, 다른 개발자와의 소통을 위해 가장 중요한 활동 중의 하나가 좋은 이름 짓기이다. 변수 이름, 함수(메서드) 이름, 클래스 이름을 짓는데 시간을 투자하라. 이름을 통해 변수의 역할, 함수의 역할, 클래스의 역할에 대한 의도를 드러내기 위해 노력하라. 연속된 숫자를 덧붙이거나(a1, a2, ..., aN), 불용어(Info, Data, a, an, the)를 추가하는 방식은 적절하지 못하다.

🔔 축약하지 않는다

의도를 드러낼 수 있다면 이름이 길어져도 괜찮다.

누구나 실은 클래스, 메서드, 또는 변수의 이름을 줄이려는 유혹에 곧잘 빠지곤 한다. 그런 유혹을 뿌리쳐라. 축약은 혼란을 야기하며, 더 큰 문제를 숨기는 경향이 있다. 클래스와 메서드 이름을 한 두 단어로 유지하려고 노력하고 문맥을 중복하는 이름을 자제하자. 클래스 이름이 Order라면 shipOrder라고 메서드 이름을 지을 필요가 없다. 짧게 ship()이라고 하면 클라이언트에서는 order.ship()라고 호출하며, 간결한 호출의 표현이 된다.

  • 객체 지향 생활 체조 원칙 5: 줄여쓰지 않는다 (축약 금지)

🔔 공백도 코딩 컨벤션이다

if, for, while문 사이의 공백도 코딩 컨벤션이다.

🔔 공백 라인을 의미 있게 사용한다

공백 라인을 의미 있게 사용하는 것이 좋아 보이며, 문맥을 분리하는 부분에 사용하는 것이 좋다. 과도한 공백은 다른 개발자에게 의문을 줄 수 있다.

🔔 space와 tab을 혼용하지 않는다

들여쓰기에 space와 tab을 혼용하지 않는다. 둘 중에 하나만 사용한다. 확신이 서지 않으면 pull request를 보낸 후 들여쓰기가 잘 되어 있는지 확인하는 습관을 들인다.

🔔 의미 없는 주석을 달지 않는다

변수 이름, 함수(메서드) 이름을 통해 어떤 의도인지가 드러난다면 굳이 주석을 달지 않는다. 모든 변수와 함수에 주석을 달기보다 가능하면 이름을 통해 의도를 드러내고, 의도를 드러내기 힘든 경우 주석을 다는 연습을 한다.

🔔 IDE의 코드 자동 정렬 기능을 활용한다

IDE의 코드 자동 정렬 기능을 사용하면 더 깔끔한 코드를 볼 수 있다.

IntelliJ IDEA: ⌥⌘L, Ctrl+Alt+L
Eclipse: ⇧⌘F, Ctrl+Shift+F

🔔 Java에서 제공하는 API를 적극 활용한다

함수(메서드)를 직접 구현하기 전에 Java API에서 제공하는 기능인지 검색을 먼저 해본다.
Java API에서 제공하지 않을 경우 직접 구현한다.
예를 들어 사용자를 출력할 때 사용자가 2명 이상이면 쉼표(,) 기준으로 출력을 위한 문자열은 다음과 같이 구현 가능하다.

List<String> members = Arrays.asList("pobi", "jason");
String result = String.join(",", members); // "pobi,jason"

🔔 배열 대신 Java Collection을 사용한다

Java Collection 자료구조(List, Set, Map 등)를 사용하면 데이터를 조작할 때 다양한 API를 사용할 수 있다.
예를 들어 List에 "pobi"라는 값이 포함되어 있는지는 다음과 같이 확인할 수 있다.

List<String> members = Arrays.asList("pobi", "jason");
boolean result = members.contains("pobi"); // true

소감

문제를 설계하고 코드를 작성하면서 코드 리펙토링에 대한 필요성을 느껴 코드 리펙토링에 대한 습관과 가독성을 위해서 좋은 네이밍 습관을 들여야겠다는 생각도 들었다.

이것저것 시행착오를 겪으면서 내 자신의 코딩 습관에 대한 피드백을 스스로 가질 수 있는 의미 있는 주차였던 것 같다.

profile
코어의 서버 탐험기

2개의 댓글

comment-user-thumbnail
2022년 11월 3일

잘보고갑니다!

1개의 답글