[CN] Array.reduce() 영혼까지 뜯어보기

곽재훈·2024년 4월 22일
0
post-thumbnail

여는 글

오늘은 내배캠 2주차다. 오늘 갑자기 팀 편성이 바뀌게 되었는데, 언젠가 바뀔 것이라는 걸 알았지만 갑작스럽게 바뀌니 아쉽고 섭섭하기도 하다. 처음에는 어색하기만 했는데 언제 이렇게 정이 들어버린 걸까나.
그래도 새로 만나게 된 팀원분들도 모두 성격도 좋으시고 친절하게 대해주시니 여기서도 다른 분들과 친해질 수 있기를!

오늘의 JS 공부

사실 최근에 프로그래머스 왕기초 코스를 돌면서 filter()와 map() 메서드를 종종 활용하고는 했었다. 그런데 reduce()와 match()는 단 한 번도 쓴 적이 없었는데, 그 이유는 공식 MDN 문서에 들어가자 보이는 복잡한 매개변수들.. 때문에 보기 싫었었다. 특히 match()는 인자로 정규표현식을 줘야하는데 스윽 보고 나온 뒤 다시 들어가지 않았었다. 하핳.

그런에 오늘 JS 강의를 들으면서 강의 마지막 부분마다 프로그래머스로 연결된 JS 연습문제들에서 나는 한 문제당 거의 10줄의 코드를 작성했는데, 다른 사람들의 풀이를 보니 한 줄의 return 코드로 작성된 답안들이 있었다. 그리고 그 문제들에 사용된 것이 reduce(), match() 메서드여서 이번 기회에 공부해봐야겠다는 생각이 들었다.
우선 오늘은 reduce() 메서드부터 살펴보자!

1. Array.reduce();

1) Array.reduce()는 무엇인가.

일단 공식 MDN 문서부터 보자.

Array.reduce() MDN 링크

arr.reduce(callback(accumulator, currentValue, index, array), initialValue);

공식 설명을 보면 Array.reduce() 메서드는 크게 두 요소를 함수의 인자로 받는데, 콜백 함수 하나와 초기값을 받는다.

accumulator 누적된 값
currentValue 현재 값
index 현재 인덱스
array 배열
initialValue 초기값

const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  initialValue,
);
console.log(sumWithInitial);
// Expected output: 10

위의 코드가 MDN 문서에서 예시로 들어주는 코드인데, 솔직히 말해서 처음 보고는 이해 안됐다.

reduce() 함수를 사용할 때는 일반적으로 reduce(callback(acc, cur(혹은 val) idx, arr), init)의 형태로 줄여서 부르는 듯 하다.

callback함수는 배열의 모든 요소에 대해서 차례대로 callback함수를 실행하는데 reduce()가 실행된 배열에서 요소를 받아온 뒤에 cur, idx, arr등을 인자로 전달하고 callback함수를 실행한다. 그 후 callback함수의 return값을 acc에 전달하고 다시 배열의 다음 요소에 대해 callback함수를 실행한다.

즉, acc는 reduce라는 함수가 배열을 순회하는 동안 계속 누적되거나 변하는 값인 것이고, cur, idx, arr는 배열에서 새로운 값을 받을 때마다 새롭게 바뀌는 값인 것이다.

2) 실제로 함수 사용해보기.

const arr = [ 1, 3, 5, 7, 9 ];

function callback(acc, cur, idx) {
    console.log(idx);
    return acc + cur;
}

const result = arr.reduce(callback, 0);

위의 코드는 익명 함수를 써서 이렇게,

const arr = [ 1, 3, 5, 7, 9 ];

const result = arr.reduce(callback(acc, cur, idx) {
	console.log(idx);
    return acc + cur;
}, 0);

혹은 화살표 함수를 써서 이렇게,

const arr = [ 1, 3, 5, 7, 9 ];

const result = arr.reduce((acc, cur, idx) => {
	console.log(idx);
    return acc + cur;
}, 0);

와 같이 표현할 수도 있을 것이다.
위의 세 개의 코드는 모두 동일한 기능을 담고 있다.

참고로 우리가 일반적으로 함수를 실행할 때는

callback();

과 같이 괄호를 붙여 사용하지만, 콜백함수의 인자로써 함수를 사용할 때는

const result = arr.reduce(callback, 0);

과 같이 ()를 붙이지 않고 사용해야 한다.

위와 같이 작성된 함수가 작동되는 방식을 그림으로 그려보면,

이렇게 정리해볼 수 있다.

const arr = [ 1, 3, 5, 7, 9 ];

function callback(acc, cur, idx) {
    console.log(idx);
    return acc + cur;
}

const result = arr.reduce(callback, 0);

console.log("redece 함수 출력값 =>", result);

redece 함수 출력값 => 25

reduce 메서드의 결과값을 담은 result의 값을 출력해보면 25가 출력되는 걸 알 수 있는데, 결과값을 배열로 반환하는 map이나 filter 메서드와는 달리, reduce 메서드는 결국 가장 마지막에 실행된 callback함수의 리턴값을 reduce 메서드의 최종 리턴값으로 반환한다는 것을 기억해야 한다.

3) 주의할 점. initialValue

arr.reduce(callback(accumulator, currentValue, index, array), initialValue);

함수를 사용할 때 주의할 점이 있다. 이것은 내가 헷갈려서 정리하는 것인데, 처음에 나는 reduce() 메서드에 대해 공부할 때 initialValue의 역할에 대해 잘 몰라서 함수의 결과값이 원하는 것과 다르게 나왔었다.

함수를 다시 살펴보면 callback 함수와 함께 initialValue를 인자로 받는 것을 볼 수 있다. 일반적으로 initialValue를 전달한다면 initialValue가 accumulator의 값이 된다. 이는 옵션값이며 initialValue를 전달하지 않으면 Array의 0번 인덱스가 대신 accumulator의 값이 되고 Array 1번 인덱스부터 callback함수의 인자로 넘어가게 된다. 그런데 여기서 문제가 발생할 수가 있다.

위의 사진은 initialValue를 옵션으로 전달하지 않았을 경우의 예시이다. 기존의 작동 방식과 크게 다르지 않아 보이지만, 가장 중요한 것은 arr[0], 즉 Array의 0번 인덱스가 cur이 아닌 acc의 인자로 전달된다는 것이고 이는 문제가 될 수 있다.

예를 들어서 1부터 5까지 5개의 숫자를 담은 Array가 있다고 가정해보자. 나는 Array의 모든 요소마다 10을 더해서 합한 숫자를 알고 싶다.

그러면 reduce를 통해 이런 코드를 짤 수 있다.

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

function plusTen(acc, cur) {
	return cur + 10 + acc;
}

const answer1 = arr.reduce(plusTen, 0);
const answer2 = arr.reduce(plusTen);

console.log('answer1', answer1);
console.log('answer2', answer2);

answer1의 경우 initialValue 옵션이 있는 경우이고, answer2는 initialValue가 없는 경우이다. 계산했을 때 둘의 결과가 다른데 왜 이런 결과가 발생하는 것일까? 그건 다시 코드를 도식화한 그림을 보면서 이야기해보자.

왼쪽의 경우 init(initialValue)으로 0이 전달된 모습이고, 오른쪽의 경우 init에 아무것도 전달되지 않은 모습이다.

왼쪽은 init이 존재하기 때문에, recude 함수가 실행될 때 init의 값을 acc에 할당하고 callback함수를 호출한다. 그 덕분에 Array의 0번 인덱스의 값인 1부터 callback함수에 제대로 전달되고 있다.
그러나 오른쪽의 경우에는 init이 없기 때문에 reduce() 메서드가 callback함수를 실행하기 전에 acc에 initialValue대신 0번 인덱스의 값을 할당한다. 보이는가? 이렇게되면 0번 인덱스에서는 내가 모든 인덱스마다 적용하고자 했던 callback함수의 코드가 작동하지 않는 것이다. 그리고 이게 initialValue가 중요한 이유다.

요약적으로 말하자면, initialValue가 option으로 주어지지 않으면, 배열의 0번 인덱스에서는 callback함수가 작동하지 않는다. 왜냐면 0번 인덱스가 initialValue 대신에 acc에 할당되기 때문.

물론 반드시 initialValue를 넣어야 한다는 이야기는 절대 아니다. 만약 initialValue의 유무에 따른 차이를 정확히 이해하고 있다면 reduce() 메서드는 사용자의 의도대로 동작하겠지만, 그 쓰임새를 정확히 이해하고 있지 않으면 함수가 의도하지 않은 방향으로 작동할 가능성이 있다는 것을 이해해야 한다는 점을 강조하고 싶었다.

4) 프로그래머스에서 활용해보기

위의 개념을 충분히 이해했다면 프로그래머스에서 reduce를 활용하여 문제를 풀어보자.

프로그래머스 문제 바로가기

그럼 이만!

profile
개발하고 싶은 국문과 머시기

0개의 댓글