ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 11/3 TIL | 난 몰랐어 코테가 이리 다채로운지!🕺🏻
    📝 기록/매일의 기록 2022. 11. 3. 20:09

    어제오늘의 코딩 도장 문제 풀이! 프로그래머스의 체육복이라는 문제였고, 어제는 Java, 오늘은 JavaScript로 문제를 풀었다.

    어제 오늘자 코딩 도장 문제 풀이 Pull Requests

    여담으로 코딩 도장 레포지토리를 지난 TIL에서 올렸던 내용을 포함해 엄청 다채롭게 꾸며나가는 중인데, 위 사진의 라벨링은 아래와 같은 내용으로 활용되고 있다! 꾸준히 업로드하는 레포지토리에 나름의 정책을 만들고 어떤 식으로 하는 게 더 좋을지를 고민해보는 과정이 참 즐겁다. 이게 더 꾸준히 할 수 있게 하는 원동력인 거 같고, 코딩 도장을 수료한다고 하더라도 계속 1일 1문제를 풀어야겠다 생각했다!

    - Java: 자바로 푼 문제
    - JavaScript: 자바스크립트로 푼 문제
    - should try again: 다시 풀어봐야 할 문제
    - solved again: 다시 한번 푼 문제
     

    10/27 TIL | 문서화는 나의 힘! Github Issue Template을 활용하여 문서 손쉽게 작성하기.

    오늘부터 코딩 도장 문제를 풀기 전에 어떻게 문제를 풀 것인지 글로 생각을 정리하고 문제를 푸는 것으로 새로운 가이드가 주어졌다! 가이드에서는 README.md 파일에 작성하라고 되어있었지만,

    bohyunkang.tistory.com


    무튼 각설하고 오늘자 코딩 도장 문제 풀이 설명을 시작해보자!

    문제 설명

    점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.

    전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.

    제한 사항

    - 전체 학생의 수는 2명 이상 30명 이하입니다.
    - 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
    - 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
    - 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
    - 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.

    입출력 예

    n lost reverse return
    5 [2, 4] [1, 3, 5] 5
    5 [2, 4] [3] 4
    3 [3] [1] 2

    💡문제 풀이 전 작성한 How to solve it?

    💡How to solve 체육복?

    문제를 풀기 전에 어떻게 풀 것인지 먼저 생각하고, 다 풀고 나서 회고를 하는 How to solve it?을 작성했다. 문제 풀이 전에는 우선 이해와 계획만 작성한다! 이렇게 이해하고 계획을 세워 아래와 같은 테스트 코드와 솔루션 코드가 완성하였다! (오늘자 JavaScript 코드만 첨부함)

    [📃 solution.js - 문제 풀이 코드]

    export default function solution(n, lost, reserve) {
      // 순서를 보장하지 않기 때문에 lost, reserve를 정렬해줘야 한다.
      // 이때 lost와 reserve에 동시에 존재하는 학생은 본인이 그 여벌을 입어야 하기 때문에 제거한다.
      const sortedLost = lost.filter((i) => !reserve.includes(i)).sort();
      const sortedReserve = reserve.filter((i) => !lost.includes(i)).sort();
    
      // 전체 학생 수에서 잃어버린 학생수를 뺀다. => 당장 체육 수업을 들을 수 있는 학생 수.
      let result = n - sortedLost.length;
    
      // 정렬된 잃어버린 학생 배열을 forEach로 순회한다.
      sortedLost.forEach((lostItem) => {
        // 잃어버린 학생 번호가 여벌이 있는 학생 번호의 앞, 뒤번호라면
        if (sortedReserve.includes(lostItem - 1)) {
          // 해당 인덱스를 찾아 -1로 바꾼 뒤 체육 수업을 들을 수 있는 학생을 1명 추가한다.
          const index = sortedReserve.indexOf(lostItem - 1);
          sortedReserve[index] = -1;
          result += 1;
          return;
        }
    
        if (sortedReserve.includes(lostItem + 1)) {
          const index = sortedReserve.indexOf(lostItem + 1);
          sortedReserve[index] = -1;
          result += 1;
        }
      });
    
      return result;
    }

    파라미터로 전체 학생 수(n)와 체육복을 잃어버린 학생 배열(lost), 여분의 체육복이 있는 학생 배열(reserve)을 입력받고, lost와 reserve를 정렬한다. 정렬하는 이유는 우선 순서가 보장되지 않기 때문에 오름차순으로 한번 정렬하여야 한다. 이때 정렬하면서 lost와 reserve에 동시에 존재하는 학생은 그 여벌을 자신이 입어야 하기 때문에 제거하여 필터링한다.

    일단 전체 학생 수에서 잃어버린 학생 수를 뺀 값을 체육 수업을 들을 수 있는 학생(result)에 할당한다. 이 학생들은 당장 체육 수업을 들을 수 있는 학생들이기 때문이다! 그러고 나서 정렬된 잃어버린 학생 배열(sortedLost)을 forEach로 순회하여 여벌이 있는 학생 배열(sortedReserve)에 잃어버린 학생 번호가 자신의 앞 혹은 뒤 번호이면 해당 인덱스를 찾아 -1로 바꾸고, result에 1을 증가시킨다. 이때 인덱스를 -1로 바꾸는 이유는 이 값은 순회하지 않아야 하기 때문이다!

    [📃 solution.test.js - 테스트 코드]

    import solution from './solution';
    
    test('체육 수업을 들을 수 있는 최종 인원을 구해라.', () => {
      expect(solution(5, [2, 4], [1, 3, 5])).toBe(5);
      expect(solution(5, [2, 4], [3])).toBe(4);
      expect(solution(3, [3], [1])).toBe(2);
    });

    모듈을 따로 분리하지 않아서 테스트 케이스가 하나뿐이다. 다음에는 더 쪼개서 진행해보도록 하자!

    💡문제 풀이 후 작성한 회고

    How to solve it? 회고까지 작성한다. 우선 이 풀이는 어제 Java 풀이랑 똑같은데, 어제는 for문을 두 번 돌려서 풀었기 때문에 오늘은 for문을 사용하지 않고 풀어보았다. 근데 그렇게 풀어보니까 문제 풀이를 동일하게 가져가기보다는 자바스크립트에서는 어떻게 풀어야 더 효율적인지 고려하면서 다시 푸는 게 좋겠다는 생각이 들었다. 이 부분은 다음 코딩 도장 문제에 반영할 수 있도록 하자!

    더군다나 오늘의 문제 풀이는 불변성이 지켜지지는 않은? 느낌이라 이렇게 풀어도 되나.. 싶기는 하다! 인덱스 값을 -1로 수정한다는 게 조금 께름칙하긴 한데... 다음 문제 풀이에서는 불변성을 지킬 수 있게끔 작성을 해보는 것도 시도해볼 부분이라고 느꼈다.

    📝 코딩 도장 사람들의 문제 풀이

    쥬쥬's 문제 풀이

    function solution(n, lost, reserve) {
      const answer = countParticipants(n, lost, reserve);
      return answer;
    }
    
    function initialCounts(n, lost, reserve) {
      return [...Array(n)]
        .fill(1)
        .map((count, i) => {
          if (lost.includes(i + 1)) {
            return count - 1;
          }
    
          return count;
        })
        .map((count, i) => {
          if (reserve.includes(i + 1)) {
            return count + 1;
          }
    
          return count;
        });
    }
    
    function processedCounts(counts) {
      return [...Array(counts.length).keys()]
        .filter((i) => counts[i] === 0)
        .reduce((acc, i) => {
          if (acc[i - 1] > 1) {
            return Object.values({ ...acc, [i - 1]: 1, [i]: 1 });
          }
    
          if (acc[i + 1] > 1) {
            return Object.values({ ...acc, [i]: 1, [i + 1]: 1 });
          }
    
          return acc;
        }, counts);
    }
    
    function countParticipants(n, lost, reserve) {
      return processedCounts(initialCounts(n, lost, reserve))
        .filter((count) => count > 0)
        .length;
    }

    🔼 쥬쥬의 풀이는 초기 체육복 개수를 담은 배열을 만들고(initialCounts), 나눠주고 뒤의 배열을 만들어서(processedCounts), 체육복 개수가 1 이상인 학생 수를 세서 반환(countParticipants)하는 식이었다. 나랑은 또 다른 방식으로 접근해서 신선했음!

    뚜루's 문제 풀이

    const pipe = (...processes) => (initialValue) => processes.reduce((result, process) => process(result), initialValue);
    
    const solution = (student, lost, reserve) => pipe(
      originStatus,
      reflectReserve(reserve),
      reflectLost(lost),
      borrowFromBackAndForth,
      countHaveClothMoreThanOne,
    )(student);
    
    const originStatus = (studentCounts) => Array
      .from({ length: studentCounts }, (_, index) => index + 1)
      .reduce((map, number) => map.set(number, 1), new Map());
    
    const reflectReserve = (reserves) => (status) => {
      const copy = new Map();
    
      status.forEach((value, key) => (reserves.includes(key)
        ? copy.set(key, value + 1)
        : copy.set(key, value)));
    
      return copy;
    };
    
    const reflectLost = (lost) => (status) => {
      const copy = new Map();
    
      status.forEach((value, key) => (lost.includes(key)
        ? copy.set(key, value - 1)
        : copy.set(key, value)));
    
      return copy;
    };
    
    const borrowFromBackAndForth = (status) => {
      const copy = new Map();
    
      status.forEach((value, key) => {
        copy.set(key, value);
      });
    
      copy.forEach((value, key) => {
        if (value === 0) {
          if (copy.has(key - 1) && copy.get(key - 1) > 1) {
            copy.set(key - 1, copy.get(key - 1) - 1);
            copy.set(key, 1);
    
            return;
          }
    
          if (copy.has(key + 1) && copy.get(key + 1) > 1) {
            copy.set(key + 1, copy.get(key + 1) - 1);
            copy.set(key, 1);
          }
        }
      });
    
      return copy;
    };
    
    const countHaveClothMoreThanOne = (status) => {
      const studentCanTakeLesson = new Map();
    
      status.forEach((value, key) => {
        if (value >= 1) {
          studentCanTakeLesson.set(key, value);
        }
      });
    
      return studentCanTakeLesson.size;
    };

    🔼 뚜루님은 파이프를 구현해서 문제를 풀이하셨더라! 파이프를 구현해본 적이 없어 좀 생소했지만 이렇게도 문제 풀이가 된다는 것이 신기했음! 알면 알수록 신기한 프로그래밍의 세계~!

    제나's 문제 풀이

    function solution(n, lost, reserve) {
      const realLost = lost.sort((a, b) => a - b).filter((el) => !reserve.includes(el));
      let realReserve = reserve.sort((a, b) => a - b).filter((el) => !lost.includes(el));
    
      const finalStudent = realLost.filter((lostEl) => {
        const abs = realReserve.find((reserveEl) => Math.abs(lostEl - reserveEl) === 1);
    
        if (!abs) {
          return true;
        }
    
        realReserve = realReserve.filter((reserveEl) => reserveEl !== abs);
      });
    
      const answer = n - finalStudent.length;
    
      return answer;
    }

    🔼 제나의 문제 풀이는 여벌 체육복을 가져온 학생 중에 도난 당한 학생과 번호 차이가 1 학생을 구하여 만약 차이가 1 아닌 경우 그대로 도난당한 학생에게 return 한다. 만약 차이가 1이면 사람에게만 빌려줄 있으므로 여벌체육복이 있는 학생에서 제외하는 방식으로 구현! 앞뒤를 비교해서 차이가 1인지를 확인하기 위해 Math.abs() 사용! (이런 생각이 나는 안 떠올랐다...ㅋ)

    👏 대망의 홀맨님 문제 풀이!

    function solution(n, lost, reserve) {
      const noSpares = diff(lost, reserve).sort();
      const spares = diff(reserve, lost);
    
      return noSpares.reduce(({ attendance, spares }, index) => {
        const target = [index - 1, index + 1].find(s => spares.includes(s));
        return target
          ? { attendance: attendance, spares: spares.filter(s => s !== target) }
          : { attendance: attendance - 1, spares: spares };
        }, { attendance: n , spares }).attendance;
    }
    
    const diff = (a, b) => a.filter(x => !b.includes(x));

    🔼 홀맨님의 문제 풀이는 reduce를 사용해서 참석할 수 있는 학생 attendance와 spares가 담긴 객체를 연산하고 반환하게끔 작성하셨더라. 전혀 생각도 못해본 풀이 방법이라 이 문제를 이렇게도 풀 수 있구나 하고 또 대단한 걸 배웠다.. 사실 프로그래머스에 올라간 다른 사람들 풀이를 보면 for문으로 해결한 경우가 대부분이어서 고차 함수를 써서 풀이를 한 코드가 엄청 소중한데, 그중에서도 단연코 홀맨님의 코드는 으뜸이다👍🏻 궁극의 발전 방법은 최상위의 행동과 습관을 따라 하는 것이다. 어느 분야든 항상 최고를 닮으려고 노력하자!


    지난주부터 작성하게 된 How to solve it은 문제 풀이 전에는 계획을 세우고, 풀고 나서는 회고를 하는 방식으로 진행된다. 덕분에 나의 코딩 도장은 꽤 다채로워졌다. 매일 아침에 코딩 테스트 한 문제 푸는 게 이렇게 즐거울 줄이야!! 코테 공부가 항상 스트레스로 다가왔던 나에게 How to solve it은 그야말로 savior 그 자체이다..

    새벽에 구구절절 남겼던 나의 후기..

    물론 이 How to solve it이 단기간에 코딩 실력을 늘려주는 치트키는 아니다. 하지만 이렇게 하나하나씩 원칙을 만들어 그걸 지켜서 꾸준히 해나가면 나도 모르는 사이에 엄청 멀리 도달해 있을 것이라는 생각이 들었다. 나의 코테 大성공시대 이제부터 시작이다!!! 파이팅💪

    덤벼라 코테놈들아!!!