[알고리즘] 카카오 블라인드 2019 - 1. 오픈채팅방 풀이 및 내 코드 리뷰 (JS)

문제를 풀어보고 풀이한 코드, 내가 생각한 주요 포인트, 개선할 점, 더 공부해볼점에 대해서 기록하고자 한다.

이 문제는 제목에도 나와있듯이 카카오 블라인드 2019에 진행되었던 테스트이고
지금은 아래의 링크에서 문제를 확인해볼수 있다.
https://programmers.co.kr/learn/courses/30/lessons/42888

0. 개요

내가 문제를 풀 당시 코드에 신경썼던 부분은 읽을 수 있는 코드와, 상수를 잘 관리해보자였다.
문제에는 채팅방의 기록에 3가지 타입의 동작이 가능하고이에 해당되는 메시지가 있었다.
그래서 이 것을 내가 사용하던 언어인 자바의 Enum 처럼 관리해보려고 하였다.

또한 ES6의 문법을 잘 활용하고자 했다.
그래서 Optional한 Property추가 나 String Interpolation을 가미해보았다.

1. 첫 풀이한 코드

function solution(recordList) {
  const Operation = {
    Enter: { key: 'Enter', message: '들어왔습니다' },
    Leave: { key: 'Leave', message: '나갔습니다' },
    Change: { key: 'Change', message: '' },
  };

  const processedRecordList = recordList.map((record) => {
    const divided = record.split(' ');
    return {
      uid: divided[1],
      operation: divided[0],
      ...(divided[2] && { newNickname: divided[2] }),
    };
  });

  const uidNicknameMap = new Map();
  processedRecordList.forEach((record) => {
    if (Operation.Leave.key !== record.operation) {
      uidNicknameMap.set(record.uid, record.newNickname);
    }
  });

  const result = [];
  for (let record of processedRecordList) {
    const { uid, operation } = record;

    if (Operation.Change.key === operation) {
      continue;
    }

    result.push(
      `${uidNicknameMap.get(uid)}님이 ${Operation[operation].message}.`,
    );
  }
  return result;
}

2. 내가 생각한 포인트

1번 문제이니만큼 크게 어려웠던 점은 없는듯하고,
카카오의 해설에 나와있듯이 연관배열(맵)을 사용하면 쉽게 풀 수 있던 문제였다.

나는 내 코드에 포인트를 더 주고 싶었다.
(똑같은 문제를 풀었지만, 채점을 하시는분이 좀 더 봐주셨으면 했다. 지금생각하니깐 좀 쑥쓰럽다.)
그리하여 ES6를 능숙하게 사용하는 것, JS에서 Enum등을 다루는 법을 더 가미해보고자 하였다.

3. 개선할점

변수들을 한군데에 모으고 주석 달기

먼저 눈에 띄는것은 변수들이 함수의 중간중간에 선언이 되어서
어떤 변수가 스코프 내에서 사용되는지 알기가 어려웠다.
그래서 아래와 같이 고쳐보았다.

function solution(recordList) {
  // Operation const object 선언
  const Operation = {
    Enter: { name: 'Enter', message: '들어왔습니다' },
    Leave: { name: 'Leave', message: '나갔습니다' },
    Change: { name: 'Change', message: null },
  };

  const uidNicknameMap = new Map(); // uid - nickname을 연관지을 객체
  const result = []; // 채팅 결과를 담을 객체
  
  ...
  // 구현 부분 생략

  return result;
}

주석을 통해 간략한 설명을 달고 변수들이 어떤 용도로 쓰이는지 명시해 주었다.

불필요한 로직을 줄이고, 코드 다듬기

recordList
    // record를 의미가 담긴 객체 형태로 변환한다
    .map((record) => {
      const [operation, uid, nickname] = record.split(' ');

      // 닉네임이 있다면 uid - nickname 맵에 넣어준다
      if (nickname) {
        uidNicknameMap.set(uid, nickname);
      }

      return {
        operation: Operation[operation],
        uid,
        ...(nickname && { nickname }),
      };
    })

첫 번째로는 채팅 기록을 split하여 divided라는 변수에 담아주었는데 이를
ES6의 Array Destructre 구문을 이용해 다듬어 주었다.
또한 newNickname이라는 변수이름을 수정하였다.
처음입장시에는 newNickname보다는 그냥 nickname이 의미상으로 더 맞는듯 했다.
Enter를 처음하는 사람일 수 도 있었기 때문이다!

두 번째로는 forEach를 여러번 사용을 하였는데, 한 번은 없어도 되었다.
map을 통해 객체를 변환하는 과정에서 uid - nickname 연관을 동시에 수행해주었다.

세 번째로는 반환되는 객체에 operation 속성에는 operation 문자열 그대로를 넣었지만,
코드를 다듬을때는 내가 객체화 하였던 상수들을 넣었다.
이제는 newRecord.operation.message를 통해서 메시지에 바로 접근이 가능하다.

그리고 추가적으로 설명을 하자면

        ...(nickname && { nickname }),

이 구문은 닉네임 변경을 하는 동작에는 nickname 변수가 없다. (undefined)
그래서 닉네임 속성을 추가할 필요가 없었고, 닉네임이 존재할 때만 닉네임 속성을 추가할 수 있게 하였다.

네 번째로는 위쪽 map()과 마지막으로 결과들을 도출해내는 forEach()를 연결해서 작성해주었다.

    .forEach(({ uid, operation }) => {
      if (operation === Operation.Change) return;
      result.push(`${uidNicknameMap.get(uid)}님이 ${operation.message}.`);
    });

Object Destructring을 통해서 uid, operation을 즉시 사용할 수 있게 하였다.
또한 Operation.Change는 리턴하게끔하여 continue 되도록 유도하였다.
그 다음에는 벡틱을 사용해서 문자열을 합치는 문법을 사용해 주었다.

최종 수정 코드

function solution(recordList) {
  // Operation const object 선언
  const Operation = {
    Enter: { name: 'Enter', message: '들어왔습니다' },
    Leave: { name: 'Leave', message: '나갔습니다' },
    Change: { name: 'Change', message: null },
  };

  const uidNicknameMap = new Map(); // uid - nickname을 연관지을 객체
  const result = []; // 채팅 결과를 담을 객체

  recordList
    // record를 의미가 담긴 객체 형태로 변환한다
    .map((record) => {
      const [operation, uid, nickname] = record.split(' ');

      // 닉네임이 있다면 uid - nickname 맵에 넣어준다
      if (nickname) {
        uidNicknameMap.set(uid, nickname);
      }

      return {
        operation: Operation[operation],
        uid,
        ...(nickname && { nickname }),
      };
    })
    .forEach(({ uid, operation }) => {
      if (operation === Operation.Change) return;
      result.push(`${uidNicknameMap.get(uid)}님이 ${operation.message}.`);
    });

  return result;
}

최종 수정한 코드는 위와 같다. 변수들도 위에 잘 나타나있고, 용도도 설명되었다.
또한 코드를 forEach, for를 혼잡해서 쓰는 것도 없앴고. 한 번 forEach가 낭비되는 것도 줄였다.
더 읽기 좋아진것 같다!! (내가 보기엔!!)

추가적으로 설명이 필요한 것이나, 피드백을 자유롭게 해주시면 감사하겠다!

좋은 웹페이지 즐겨찾기