Code Review _ Object

alswl5181·2021년 9월 3일
1

Javascript

목록 보기
4/6
post-thumbnail

Repl에서 문제를 풀다가 마지막쯤에 object 문제를 만나게 되었다.
이미 존재하는 객체의 value값들을 조건에 맞게 변경하고, 새로운 key값들을 추가해서 새로운 객체를 반환하는 문제였다.

단순히 객체에 key와 value를 추가하는 것은 어렵지 않지만, 조건이 주어져 있었다. 이미 존재하는 key는 그대로 두고 존재하지 않는 key만 골라서 추가해야 해서, 문제를 풀다보니 생각보다 머리가 복잡해졌다.

처음에는 결과값을 도출하는 것에만 포커스를 맞춰서 코드를 짰는데,
for문과 if~else문을 이용해서 배열을 순회하며 조건에 맞으면 key값을 추가하는 형식으로 문제를 풀었다.
하지만 문제를 풀면서 분명 이것보다 더 좋은 방법이 있을 것이고, 현재 코드는 js의 속성을 잘 활용하지 못했다는 생각이 들었다. 그래서 다시 문제를 풀어보았고, 많은 것을 배우게 되었다.

문제

아래 설명을 읽고 getExamResult 함수를 구현하세요.
인자 scores 는 다음과 같은 객체입니다. 객체의 요소의 갯수 및 키의 이름들은 달라질 수 있습니다. 객체의 값은 다음 9가지 문자열 중에서 하나를 가지고 있습니다.

'A+', 'A', 'B+', 'B', 'C+', 'C', 'D+', 'D', 'F'

{
  '생활속의회계': 'C',
  '논리적글쓰기': 'B',
  '독일문화의이해': 'B+',
  '기초수학': 'D+',
  '영어회화': 'C+',
  '인지발달심리학': 'A+',
}

인자 requiredClasses 는 다음과 같이 문자열로 된 배열입니다.

['영어회화', '기초수학', '공학수학', '컴퓨터과학개론']

다음 조건을 만족하는 객체를 리턴하도록 함수를 구현해주세요.

scores 객체가 가지고 있는 키들은 새로운 객체에 포함되어야 합니다. 단, 그 값들은 다음 원리에 따라 숫자로 바뀌어 할당되어야 합니다.

A+ => 4.5
A => 4
B+ => 3.5
B => 3
C+ => 2.5
C => 2
D+ => 1.5
D => 1
F => 0

requiredClasses 배열의 요소로는 존재하지만, scores의 키로는 존재하지 않는 항목이 있다면, 해당 요소는 새로운 객체의 키가 되고, 값으로 0을 가져야 합니다. 위에서 예시로 묘사된 객체와 배열이 인자로 들어왔다면, 다음과 같은 객체과 리턴됩니다. 요소간 순서는 다를수 있지만, 채점에 무관합니다.

// 출력값
{
  '생활속의회계': 2,
  '논리적글쓰기': 3,
  '독일문화의이해': 3.5,
  '기초수학': 1.5,
  '영어회화': 2.5,
  '인지발달심리학': 4.5,
  '공학수학': 0,
  '컴퓨터과학개론': 0,
}

🚩 문제풀이 _ 첫번째 try

아래 코드를 보면 알 수 있듯이, 객체를 배열로 만들어서 등급에 맞는 점수를 넣고, include 함수를 이용하여 key값이 있는지 없는지 일일히 비교를 해서 최종 결과값을 출력한다.

const getExamResult = (scores, requiredClasses) => {
  // 등급에 따른 점수를 인덱스로 가져올 수 있도록 이중 배열 선언
  let grade = [
    ["A+", 4.5],
    ["A", 4],
    ["B+", 3.5],
    ["B", 3],
    ["C+", 2.5],
    ["C", 2],
    ["D+", 1.5],
    ["D", 1],
    ["F", 0],
  ];
  
  // scores객체를 배열 형태로 변환
  let arr = Object.entries(scores);
  
  // arr 배열의[i][1]과(기존 object의 value값)과
  // grade[j][0]을 비교해서, 같으면 해당 점수로 value 변경
  for (let i in arr) {
    for (let j in grade) {
      if (arr[i][1] === grade[j][0]) {
        scores[arr[i][0]] = grade[j][1];
      }
    }
  }
  
  // scores객체의 key값을 담은 배열 생성 
  let keys = Object.keys(scores);

  // keys 배열이 requiredClasses 배열의 값을 포함하고 있지 않으면,
  // scores객체에 해당 이름으로 key: 0 생성
  for (let i in requiredClasses) {
    if (!keys.includes(requiredClasses[i])) {
      scores[requiredClasses[i]] = 0;
    }
  }

  return scores;
};

🚩 문제풀이 _ 두번째 try

다시 풀때는 reduce함수와 ES6의 spread 연산자를 이용하여 코드를 짜보았다.

reduce 함수의 initialValue

reduce 함수는 배열 각 요소에 대해 콜백함수를 실행하여 하나의 값을 리턴한다. reduce함수에 대한 기본적인 설명은 패스하도록 하고, 새롭게 알게된 내용만 정리해보겠다.

reduce 함수에서 initialValue란
callback의 최초 호출에서 첫 번째 인수에 제공하는 값이다. 초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용한다.
reduce 함수에는 initialValue값을 다음 처럼 줄 수 있다.

let array = [1, 2, 3, 4, 5];

array.reduce((acc, currentValue) => {
  acc + currentVal;
}, initialValue);
  • initialValue를 제공했을 때
    acc = initialValue
    currentValue = 배열의 첫번째 값

    즉, initialValue는 함수가 처음으로 불려졌을 때 accumulator값을 초기화하는 것이다.

이해를 쉽게하기 위해 예시를 들어보자.
위의 예시 코드 initialValue 값으로 10이 들어갔다고 가정해보자.
그러면 다음 표처럼 데이터가 들어가게 된다.
그럼 initialValue 값에 { }를 넣으면 어떻게 될까?

const requiredObjs = requiredClasses.reduce((result, current) => {
    result[current] = 0;
    return result;
  }, {});

위의 코드 결과값을 표로 정리해보면,
이런 방식으로 requiredClasses의 값들이 들어간 객체가 만들어질 것이다!

ES6의 spread 연산자

spread는 말그대로 배열이나 객체를 펼친다는 뜻이다.

let animal = {
  type: "animal",
  leg: 4,
  color: "white",
};

let rabbit = {
  ...animal,
  color: "pink",
  name: "rabbit",
};

console.log(animal);	// { type: 'animal', leg: 4, color: 'white' }
console.log(rabbit);	// { type: 'animal', leg: 4, color: 'pink', name: 'rabbit' }

예제 코드를 보면 rabbit 객체 안에서 animal 객체를 spread 했더니, color 속성은 덮어씌워져 pink값이 되었고, 없던 속성은 새로 생기는 것을 알 수 있다.

이 원리를 이용하여 requiredObjs를 spread 해주고, 그 다음에 scoreObj를 spread 해줌으로써, requiredObjs에 scoreObj가 덮어씌워진다. 그러면 점수가 제대로 들어가있는 기존 과목 속성들은 유지될 것이고, 들어가있지 않던 새로운 과목 속성들은 추가될 것이다.

✅ 최종 Code

let getExamResult = (scores, requiredClasses) => {
  
  // 등급에 따른 점수를 scoreMap이라는 객체로 선언
  const scoreMap = {
    "A+": 4.5,
    "A": 4,
    "B+": 3.5,
    "B": 3,
    "C+": 2.5,
    "C": 2,
    "D+": 1.5,
    "D": 1,
    "F": 0,
  };

  // requiredClasses에 담긴 과목들로 먼저 key: 0인 객체 생성
  const requiredObjs = requiredClasses.reduce((result, current) => {
    result[current] = 0;
    return result;
  }, {});

  // 출력: { '영어회화': 0, '기초수학': 0, '공학수학': 0, '컴퓨터과학개론': 0 }
  console.log(requiredObjs); 

  // score 객체 value값을 등급에서 점수로 대체한 객체 생성
  // score 객체의 key값을 저장한 배열에서 reduce 돌림
  const scoreObj = Object.keys(scores).reduce((result, current) => {
    // [scores[current]] => 성적객체[객체 key값 하나씩 대입]
    // scoreMap[scores[current]] => scoreMap[key값(등급)] => 등급에 맞는 점수
    result[current] = scoreMap[scores[current]];
    return result;
  }, {});
  
  // requiredObjs 다음에 scoreObj를 spread하면 
  // requiredObjs에 scoreObj가 합쳐진 객체를 반환
  return {
    ...requiredObjs,
    ...scoreObj,
  };
};

🤠 마무리

reduce와 spread를 썼더니 배열과 for문을 쓰지 않고 더 깔끔하게(?) 객체를 만들 수 있었던 것 같다. 이것들을 사용하는게 많이 익숙해지면 코드를 짜기까지 생각하는 시간이 훨씬 단축될 것 같은데, 아직 익숙하지 않아서 그런지 꽤 긴 시간이 걸린 것 같다.
reduce 함수를 배열 요소들의 합 구할 때 말고는 써본적이 없는데, 인자값에 따라서 그 기능이 달라진다는 것도 신기했고, 실제로 reduce가 정말 다양한 곳에 많이 쓰인다고 한다. spread 같은 경우는 진짜 너무 간단해져서 충격적이었다.

사실 코드 길이가 드라마틱하게 단축되고 그런건 아니지만, 좀 더 자바스크립트 특징을 잘 활용한 풀이법이지 않을까라고 생각한다.

profile
쿼카를 사랑하는 프론트엔드 개발자입니다 :)

0개의 댓글