게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌56 - JavaScript - 6년 전 등록

map, reduce 활용하기

함수형 프로그래밍

안녕하세요. 이번 시간에는 mapreduce 메서드에 대해 알아보겠습니다. 배열에 있는 바로 그 map과 reduce 맞습니다.

많은 분들이 forEach는 사용하시는데 map과 reduce는 잘 안 쓰시더라고요. 그리고 reduce가 뭐냐고 물어보면 덧셈하는 함수 아니냐고 하시는 분도 많이 봤습니다. ㅠㅠ 그래서 오늘은 제가 제일 좋아하는 메서드인 map과 reduce에 대해 알아보겠습니다. 제가 생각하기로는 자바스크립트 내장 메서드 중에서 제일 강력하고, 알아두면 다양한 곳에 활용할 수 있는 것이 바로 map과 reduce입니다. 얼마나 강력하냐면, 맵리듀스라고 이름지어진 프레임워크도 있습니다. (물론 오늘 다루는 내용은 아닙니다.)

map

map 메서드는 다음과 같이 사용합니다. 배열.map((요소, 인덱스, 배열) => { return 요소 });

map의 기본 원리는 간단합니다. 반복문을 돌며 배열 안의 요소들을 1대1로 짝지어 주는 것입니다. 그래서 이름이 map입니다. 매핑한다고 표현하죠. 어떻게 짝지어줄 것인가 정의한 함수를 메서드의 인자로 넣어주면 되는 것입니다.

일단 주어진 배열을 그대로 매핑해봅시다.

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.map((v) => {
  console.log(v);
  return v;
});
// 콘솔에는 1, 2, 3이 찍힘
oneTwoThree; // [1, 2, 3]
result; // [1, 2, 3]
oneTwoThree === result; // false

반복문으로 요소를 순회(1, 2, 3 순서로)하면서 각 요소를 어떻게 짝지어줄지 알려줍니다. 함수가 그냥 return v를 하기 때문에 같은 값을 그대로 짝짓습니다. 알아둘 점은, map을 실행하는 배열과 결과로 나오는 배열이 다른 객체라는 것입니다. 기존 배열을 수정하지 않고 새로운 배열을 만들어냅니다. 단, 배열 안에 객체가 들어있는 경우, 객체는 공유됩니다.

이번에는 각 요소에 1씩 더한 값을 반환해보겠습니다.

result = oneTwoThree.map((v) => {
  return v + 1;
});
result; // [2, 3, 4]

규칙적인 배열만 반환할 수 있는게 아니라, 함수 안에 적어준대로 반환할 수 있기 때문에 자유도가 높습니다.

result = oneTwoThree.map((v) => {
  if (v % 2) {
    return '홀수';
  }
  return '짝수';
});
result; // ['홀수', '짝수', '홀수']

정리하자면, map은 배열을 1대1로 짝짓되 기존 객체를 수정하지 않는 메서드입니다.

reduce, reduceRight

reduce를 덧셈 함수로 알고 계신 분들이 많은데, 대부분의 사이트에서 reduce 사용 예시를 덧셈으로 들고 있기 때문입니다. 심지어 저도 강좌에서 덧셈으로 예를 들었습니다... 하지만 reduce는 알고보면 매우 강력한 친구입니다. 함께 알아봅시다.

reduce 메서드는 다음과 같이 사용합니다. 배열.reduce((누적값, 현잿값, 인덱스, 요소) => { return 결과 }, 초깃값);

이전값이 아니라 누적값이라는 것에 주의하셔야 합니다. 누적값이기 때문에 다양하게 활용할 수 있습니다. 먼저 흔한 덧셈 예시를 살펴봅시다.

result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 1 0
// 1 2 1
// 3 3 2
result; // 6

acc(누적값)이 초깃값인 0부터 시작해서 return하는대로 누적되는 것을 볼 수 있습니다. 초깃값을 적어주지 않으면 자동으로 초깃값이 0번째 인덱스의 값이 됩니다.

result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
});
// 1 2 1
// 3 3 2
result; // 6 

초깃값을 적었을 때와 안 적었을 때의 차이점이 보시이죠?

reduceRight는 reduce와 동작은 같지만 요소 순회를 오른쪽에서부터 왼쪽으로 한다는 점이 차이입니다.  

result = oneTwoThree.reduceRight((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 3 2
// 3 2 1
// 5 1 0
result; // 6

여기까지는 흔한 덧셈 예제이지만... reduce는 덧셈 곱셈만을 위한 것이 아닙니다. 초깃값을 활용하여 어떤 일을 할 수 있는지 알아보겠습니다. map과 filter과 같은 함수형 메서드를 모두 reduce로 모두 구현할 수 있습니다.

위의 map 예제를 reduce로 만들어보겠습니다.

result = oneTwoThree.reduce((acc, cur) => {
  acc.push(cur % 2 ? '홀수' : '짝수');
  return acc;
}, []);
result; // ['홀수', '짝수', '홀수']

초깃값을 배열로 만들고, 배열에 값들을 push하면 map과 같아집니다. 이를 응용하여 조건부로 push를 하면 filter와 같아집니다. 다음은 홀수만 필터링하는 코드입니다.

result = oneTwoThree.reduce((acc, cur) => {
  if (cur % 2) acc.push(cur);
  return acc;
}, []);
result; // [1, 3]

이와 같이 sort, every, some, find, findIndex, includes도 다 reduce로 구현 가능합니다. reduce가 이들 모두의 아버지였던 것입니다. ㅠㅠ

reduce는 비동기 프로그래밍을 할 때에도 유용합니다.

const promiseFactory = (time) => {
  return new Promise((resolve, reject) => {
    console.log(time); 
    setTimeout(resolve, time);
  });
};
[1000, 2000, 3000, 4000].reduce((acc, cur) => {
  return acc.then(() => promiseFactory(cur));
}, Promise.resolve());
// 바로 1000
// 1초 후 2000
// 2초 후 3000
// 3초 후 4000

초깃값을 Promise.resolve()로 한 후에, return된 프로미스에 then을 붙여 다음 누적값으로 넘기면 됩니다. 프로미스가 순차적으로 실행됨을 보장할 수 있습니다.

반복되는 모든 것에는 reduce를 쓸 수 있다는 것을 기억하시면 됩니다.

map과 reduce 외에도, 배열의 메서드인 sort, filter, every, some, find, findIndex, includes 정도는 알아두시면 좋습니다. 오늘 reduce만 있어도 다른 메서드들을 다 구현할 수 있다는 것을 배웠기 때문에, 다른 메서드를 까먹으면 reduce로 구현하시면 됩니다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

13개의 댓글이 있습니다.
일 년 전
정말 좋은글 감사합니다. 마지막 비동기 프로그래밍부분은 프로미스에대해 공부하지못해서인지 이해못했습니다만 나머지는 good입니다!
2년 전
좋은글 감사합니다 퍼가도 될까요?
2년 전
출처만 남겨주세요.
3년 전
ㅋㅋㅋ 오야지 ㅋㅋ
3년 전
리듀스 오야지!!!!!!!!!!!
3년 전
reduce가 이들 모두의 아버지였던 것입니다. ㅠㅠ \u003c\u003c 감동적이네요..
3년 전
저 질문이 있습니다.초깃값을 명시적으로 0으로 설정하는 것과 reduce에서 초깃값을 설정하지 않을시에 0부터 시작하는 것을 같지만, console로 찍을 때만 차이가 나는건지 궁금합니다.
3년 전
네, 다만 초기값을 설정하지 않는다고 무조건 0부터 시작하는 게 아니라 첫 요소가 초기값이 됩니다.
3년 전
순환이나 탐색 할 시에, 절차식 표현 for(let i=0; i\u003carr.length; i++) 방식 말고 forEach, map과 같은 내장함수를 쓰면 명확하게 어떤 점에서 더 좋나요 ?
3년 전
메서드 체이닝을 할 수 있어서 보기 깔끔해집니다.
3년 전
감사합니다!!!!!
3년 전
내용 감사하게 잘 보았습니다. 제로초님의 코드입력한 창을 보면 vs code처럼 번호가 있고 칸칸마다 색깔이 다른데 어떻게 구현한 것인지요?방법을 알고 싶습니다.
3년 전
prism.js 썼습니다!
4년 전
근데 강의에서 map을 forEach로 구현하는게 억지라고 하셨는데 왜 억지인지 잘 이해가 안갑니다...
4년 전
map, reduce 개념 너무 햇갈리고 어려웠는데 쉽게 이해되네요 설명과 예시 감사합니다 비동기 부분은 좀 어렵네요 까먹어서 다시 한번 공부해봐야겠어요ㅠ
4년 전
안녕하세요 제로초님 map을 reduce로 만들어보는 예제에서 (cur % 2 ? '홀수' : '짝수') 저부분의 조건이 이해가 되지 않습니다 ㅜㅜ 삼항연산자니까 (조건 ? 참 : 거짓) 인건데 조건에 cur % 2 === 1이 아닌 cur % 2 라고만 적고 ? '홀수' : '짝수' 라고 적어도 되는 이유가..무엇인가요?
4년 전
숫자인 경우 결괏값이 0이 아닌 경우는 모두 true가 됩니다. 0이면 false로 치고요
4년 전
아!! 너무 감사드립니다!!
6년 전
혹시 reduce를 도는 중간에 break나 빠져나가게 할려면 어케해야하나요..? 특정 조건만족하는것만 찾고 다음 코드로 넘어가게 할려고 합니다!!
6년 전
every나 some 쓰세요
6년 전
map 처음 예제 8줄에 결과값에 주석이 쳐져있지 않은것 같습니다.
6년 전
앗! 감사합니다.