reduce란 무엇인가 #


수학을 배우다보면 정의 -> 활용의 순서로 배우게 됩니다.

reduce란 무엇인지 알아봅시다.

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

reduce는 reducer함수를 실행한다.. 아리송합니다. reducer는 뭘까요?

리듀서 함수는 네 개의 인자를 가집니다.

  • 1️⃣ 누산기 (acc)
  • 2️⃣ 현재 값 (cur)
  • 3️⃣ 현재 인덱스 (idx)
  • 4️⃣ 원본 배열 (src)
    리듀서 함수의 반환 값은 누산기에 할당되고, 누산기는 순회 중 유지되므로 결국 최종 결과는 하나의 값이 됩니다.

그렇습니다 reduce는 reducer함수를 실행하는 실행부이고 reducer는 concrete한 함수를 지니고 있는 구현체입니다.




그래서 뭔말이야? #


reduce 함수가 하고 싶은 일을 요약하자면

시퀀스들을 하나의 어떤 값으로 뽑아낸다는 것입니다.

예를 들어보죠

[1, 2, 3, 4, 5, 6, 7, 8, 9]

위의 배열이 있다고 합시다. 위 배열의 인자를 모두 더한 하나의 값으로 만들고 싶다면 어떻게 해야 할까요?

반복문을 실행하면 되겠습니다

let sum = 0;
[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(a => sum += a);
console.log(sum); // 45

이런식입니다.

전혀 reduce 함수를 사용하지 않아도 구현 할 수 있습니다.

reduce 함수를 사용하면 이런 느낌이겠습니다.

[1,2,3,4,5,6,7,8,9].reduce((acc, cur)=> acc + cur);

와! 좀 짧아졌네요!

reduce 함수는 reducer 함수의 인자가 3개일 때와 2개일 때 다르게 작동합니다. 2개인 경우 시퀀스의 첫 인자를 시작 값으로 합니다.

위의 예를 들면 reducer 함수가 인자를 2개 받으므로 1이 위 reducer 함수의 시작값이 됩니다.




reduce를 사용해 압축하기 (접기) #


하나의 상황을 가정해보죠 이런 일은 생각보다 현업에서도 있습니다.

아래의 cars라는 변수에는 객체를 담은 배열이 있습니다.

그런데 좀 정리를 하고 싶습니다. 예를 들어 make 별로 정리를 하고 싶다고 가정하죠 물론 여러 방법이 있겠습니다만 reduce를 사용해 이 문제를 해결해봅시다.

// cars라는 배열을
const cars = [
    { make: 'audi', model: 'r8', year: '2012' },
    { make: 'audi', model: 'rs5', year: '2013' },
    { make: 'ford', model: 'mustang', year: '2012' },
    { make: 'ford', model: 'fusion', year: '2015' },
    { make: 'kia', model: 'optima', year: '2012' }
];

// 이런 형태로 만들고 싶습니다.
{
    "audi": [
        {
            "make": "audi",
            "model": "r8",
            "year": "2012"
        },
        {
            "make": "audi",
            "model": "rs5",
            "year": "2013"
        }
    ],
    "ford": [
        {
            "make": "ford",
            "model": "mustang",
            "year": "2012"
        },
        {
            "make": "ford",
            "model": "fusion",
            "year": "2015"
        }
    ],
    "kia": [
        {
            "make": "kia",
            "model": "optima",
            "year": "2012"
        }
    ]
}

여기서 현업의 개발자라면 자동차의 maker는 이것보다 훨씬 많고 앞으로도 늘어날지 모른다라는 점과 확장 가능하면서도 유연한 코드를 작성하고 싶어 할 것입니다.

이제 여기서 reduce가 등장합니다.




예제 #


const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];


cars.reduce((acc,cur)=>{
    if(acc[cur.make]) { // 누적 값에 make가 이미 있다면
        acc[cur.make].push(cur); // 누적 값의 배열에 push
    } else {
        acc[cur.make] = [cur]; // 아니면 make를 key로 배열을 생성
    }
    return acc; // 누적 값을 다음 시퀀스 인자에 넘겨준다.
},{}) // 빈 객체부터 시작



예제의 설명 #


reduce에 친숙하지 않다면 위 코드는 살짝 머리아플 수 있습니다. 하지만 괜찮습니다.

알고보면 어려울게 하나도 없습니다.

위 코드는 빈 객체부터 시작합니다.

acc에는 빈 객체가 담겨 있습니다. 만약 빈 객체에 make라는 key가 있다면 값을 추가하고 아니라면 key를 기준으로 배열을 만들어줍니다.

이게 끝나면 다음번 순회에 acc값을 넘겨줍니다.

다음번 순회는 지난 번 acc 값이 그대로 채워진 상태로 시작합니다.

{
    audi: [{
        "make": "audi",
            "model": "r8",
            "year": "2012"  
    }]
}

이번 순회하는 cur의 make가 audi라면 if문을 통과해 배열에 push하게 됩니다. 이를 다시 다음 시퀀스에 넘기게 됩니다.

따라서 배열이 하나의 객체로 "접히는" 압축되는 함수인 것이죠.




마무리 #


보통 pipe나 go 함수를 통과해 시퀀스를 계산 및 필터링하고 reduce를 통해 모두 접어 하나의 객체나 값으로 뽑아내어 주는 것

이게 핵심입니다.

응용은 아주 많습니다. pipe나 go도 모두 reduce를 사용하고 있으니까요

pipe, go는 나중에 기회가 되면 설명하겠습니다.