본문 바로가기

Programming Language/JavaScript

[Javascript] 반복문 lover인 나에게 map과 reduce를 건네본다..❤️

배열과 메서드 파트의 과제를 풀면서 나의 안 좋은 습관을 발견해버렸다.

 

바로 모든 문제를 반복문을 써서 풀려고 하는 것이다!

물론 결과는 잘 도출된다. 문제는 코드가 복잡해지고 깔끔하지 않다는 것이다! 

 

그래서 나는 이번 포스팅을 통해

배열에서 쓰이는 반복문(forEach, for, for..or) 대신 쓸 수 있는

mapreduce 메서드 활용법을 숙지하려한다.

 

 


어떤 경우에 map을 쓸까?

map은 각 요소를 돌면서 반복 작업을 수행하고, 작업 결과물을 새로운 배열 형태로 얻을 때 사용하면 된다.

내가 풀었던 문제와 내가 쓴 답안은 다음과 같다.

 

 

문제: border-left-width를 borderLeftWidth로 변경하기

"my-short-string"같이 여러 단어를 대시(-)로 구분한 문자열을 카멜 표기법을 사용한 문자열 "myShortString"로 변경해주는 함수를 작성해보세요.

대시는 모두 지우고 각 단어의 첫 번째 글자는 대문자로 써주면 됩니다.

예시:

camelize("background-color") == 'backgroundColor';
camelize("list-style-image") == 'listStyleImage';
camelize("-webkit-transition") == 'WebkitTransition';

힌트: split을 사용해 문자열을 배열로 바꾼 다음 join을 사용해 다시 합치면 됩니다.

 

내가 쓴 답안

const camelize = (str) => {
  
  //str.split(delim)은 delim을 기분으로 문자열을 쪼개준다.
  let arr = str.split('-');
 
  for (let i = 1; i< arr.length; i++) {
  
  	//i번째 단어부터 알파벳 단위로 나눠서  backWordArr배열에 저장한다.
    let backWordArr = arr[i].split(''); 
    
    //  backWordArr배열의 0번째 알파벳을 대문자로 바꿔준 후 capital에 저장해준다.
    let capital = backWordArr[0].toUpperCase(); 
    
    //  backWordArr의 0번째 알파벳을 지우고 그 자리에 capital(대문자)를 넣어준다.
    backWordArr.splice(0, 1, capital);
    
    // backWordArr의 배열 요소를 모두 합친 후 modifiedWord라는 하나의 문자열로 만들어준다.
    let modifiedWord = backWordArr.join('');
    
    //arr배열의 i번째 요소는 modifiedWord로 바뀐다.
    arr[i] =  modifiedWord;
  } 
  
  	//arr배열의 배열 요소를 모두 합친 후 result라는 하나의 문자열로 만들어준다.
  let result = arr.join('');
  
  console.log(result);
  return result;
  
};

camelize("background-color");
camelize("list-style-image");
camelize("-webkit-transition");

 

나는 문제를 위와 같이 for문을 사용하여 풀었다. 코드가 상당히 길어져서 한눈에 이해하기 어렵다.

for문을 통해서 하고 싶었던 것은 결국 각 요소를 돌며 반복 작업을 수행하고, 작업 결과물을 새로운 배열 형태로 만든 것이다. 즉 여기서는 for문 대신 map을 사용해도 된다는 것이다.

 

이제 공식 답안을 같이 보자.

공식 답안

function camelize(str) {
  return str
    .split('-')
    .map((word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1))
    .join('');
}

 

같이 분석해보자.

 

.split('-')

str.split(delim) 메서드는 구분자(delimiter) delim을 기준으로 문자열을 쪼갠다. 

즉 여기서 str로 'my-Long-word'가 들어왔다면 split('-')을 통해 [ 'my', 'Long', 'word' ] 배열이 된다.

 

 

.map( (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) )

arr.map은 배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환해준다.

여기서 word는 배열의 요소고, index는 말 그대로 인덱스다.

 

[ 'my', 'Long', 'word' ]에서 index가 0이라면 배열의 요소를 그대로 둔다.

index가 0이 아니라면 '문자열의 첫번째 글자(word[0])을 대문자로 바꾼 것'과 'word의 1번째 글자부터 끝까지'를 더해준다.

 

참고로 문자열 내 특정 위치인 pos에 있는 글자에 접근하려면 [pos] 같이 대괄호를 이용하거나 str.charAt(pos)라는 메서드를 호출하면 된다. 위치는 0부터 시작한다.

let str = `Hello`;

// 첫 번째 글자
alert( str[0] ); // H
alert( str.charAt(0) ); // H

// 마지막 글자
alert( str[str.length - 1] ); // o

참고: https://ko.javascript.info/string#ref-779

 

문자열

 

ko.javascript.info

 

 

.join('')

map 메소드를 통해서 [ 'my', 'Long', 'word' ]는 [ 'my', 'Long', 'Word' ]라는 배열을 만들었다.

이제 이걸 join 메소드를 통해 [ 'my', 'Long', 'Word' ]의 배열 요소를 모두 합친 후 myLongWord라는 하나의 문자열로 만들어 준다. 

 

 


어떤 경우에 reduce를 쓸까?

arr.reduce는 배열을 기반으로 값 하나를 도출할 때 사용된다.

 

문법

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

인수로 넘겨주는 함수는 배열의 모든 요소를 대상으로 차례차례 적용되는데, 적용 결과는 다음 함수 호출 시 사용된다.

함수의 인수는 다음과 같다.

  • accumulator – 이전 함수 호출의 결과. initial은 함수 최초 호출 시 사용되는 초깃값을 나타냄(옵션)
  • item – 현재 배열 요소
  • index – 요소의 위치
  • array – 배열

이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(previousValue)로 사용된다.

첫 번째 인수는 앞서 호출했던 함수들의 결과가 누적되어 저장되는 '누산기(accumulator)'라고 생각하면 된다.

마지막 함수까지 호출되면 이 값은 reduce의 반환 값이 됩니다.

 

예제

reduce를 이용해 코드 한 줄로 배열의 모든 요소를 더한 값을 구해보는 코드

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

let result = arr.reduce((sum, current) => sum + current, 0);

alert(result); // 15

reduce에 전달한 함수는 오직 인수를 두 개만 받고 있다. 대개 이렇게 인수를 두 개만 받는다.

이 예제에 관한 자세한 설명은 아래 링크에 들어가서 보자.

https://ko.javascript.info/array-methods#ref-403

 

배열과 메서드

 

ko.javascript.info

 

 

그럼 이제 reduce를 통해 문제를 풀어보자

 

문제: 평균 나이 구하기

 

내가 쓴 답안

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 29 };

let arr = [ john, pete, mary ];

console.log( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28

function getAverageAge(users) {
  
  let sum = 0;
  
  for(let i = 0; i < users.length; i++){
      sum += users[i].age;
  } 
  return sum / users.length;
  
}

나는 여기에 for문을 써서 sum에  배열의 요소를 차례차례 더해줬다.

그러나 여기서 reduce를 쓰면 코드가 한 줄로 끝나게 된다.

 

 

공식 답안

function getAverageAge(users) {
  return users.reduce((prev, user) => prev + user.age, 0) / users.length;
}

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 29 };

let arr = [ john, pete, mary ];

alert( getAverageAge(arr) ); // 28
  • prev는 accumulator(이전 함수 호출의 결과)
  • user는 item(현재 배열 요소)
  • 0은 initial(함수 최초 호출 시 사용되는 초깃값)
  • user.age: 점 표기법을 이용하여 age라는 키를 가지고 있는 프로퍼티 값을 가져온다.

 

 


정리

배열 메서드에는 반복문 대신 쓸 수 있는 map과 reduce 메서드가 있다.

  • map은 각 요소를 돌면서 반복 작업을 수행하고, 작업 결과물을 새로운 배열 형태로 얻을 때 사용하면 된다.
  • reduce는 배열을 기반으로 값 하나를 도출할 때 사용된다.

앞으로 반복문을 쓰기 전에 다른 메서드를 쓸 수 있는지 생각해보자!