js reduce

이종호·2021년 4월 8일
0

JavaScript

목록 보기
16/19
post-thumbnail

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

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

리듀서 함수는 4개의 인자를 가집니다.
1. 누산기 (acc)
2. 현재 값 (cur)
3. 현재 인덱스 (idx)
4. 원본 배열 (src)

리듀서 함수의 반환 값은 누산기에 할당되고, 누산기는 순회 중 유지되므로 결국 최종 결과는 하나의 값이 됩니다.

여기까지 reduce는 배열의 어떻게 조작해 하나의 값으로 만든다.

구문

arr.reduce(callback[, initialValue])

  • callback
    배열의 각 요소에 대해 실행할 함수. 4가지 인수를 받습니다.

    • accumulator
      누산기는 콜백의 반환값을 누적합니다. 콜백의 이전 반환값 또는 콜백의 처 번째 호출이면서, initialValue를 제공한 경우에는 initialValue의 값입니다.
    • currentValue
      현재 처리할 요소
    • currentIndex[Optional]
      처리할 현재 요소의 인덱스. initialValue를 제공한 경우 0 아니면 1부터 시작합니다.
    • array[Optional]
      reduce()를 호출한 배열
  • initialValue[Optional]
    callback의 최초 호출에서 첫 번째 인수에 제공하는 값.
    초기 값을 제공하지 않으면 배열의 첫 번째 요소를 사용합니다.
    빈 배열에서 초기값 없이 reduce를 호출하면 오류가 발생합니다.

반환 값

누전 계산의 결과 값

설명

reduce()는 빈 요소를 제외하고 배열 내에 존재하는 각 요소에 대해 callback함수를 한 번씩 실행하는데, 콜백함수는 다음의 네 인수를 받습니다.

콜백이 최초 호출 떄 accumulator와 currentValue는 다음 두 가지 값 중 하나를 가질 수 있습니다.
만약 reduce()함수 호출에서 initialValue를 제공한 경우, accumulator는 initalValue와 같고
currentValue는 배열의 처 번째 값과 같습니다.
initialValue를 제공하지 않았다면, accumulator는 배열의 첫 번째 값과 같고,
currentValue는 두 번째와 같습니다.

initialValue를 제공하지 않으면, reduce()는 인덱스 1부터 시작해 콜백 함수를 실행하고,
첫 번째 인덱스는 건너 뜁니다.
initialValue를 제공하면 인덱스 0에서 시작합니다.

배열이 비어있고, initialValue도 없다면
TypeError가 발생합니다.

배열의 요소가 하나뿐이고, initialValue가 제공되지 않은 경우
또는 initalValue는 주어졌으나 배열이 빈 경우엔, 그 단독 값을 callback호출 없이 반환합니다.

다음의 예제처럼 initialValue를 제공하지 않으면 출력 가능한 형식이 세 가지 이므로, 보통 초기값을 주는 것이 더 안전합니다.

var maxCallback = ( acc, cur ) => Math.max( acc.x, cur.x );
var maxCallback2 = ( max, cur ) => Math.max( max, cur );

// initialValue 없이 reduce()
[ { x: 22 }, { x: 42 } ].reduce( maxCallback ); // 42
[ { x: 22 }            ].reduce( maxCallback ); // { x: 22 }
[                      ].reduce( maxCallback ); // TypeError

// map/reduce로 개선 - 비었거나 더 큰 배열에서도 동작함
[ { x: 22 }, { x: 42 } ].map( el => el.x )
                        .reduce( maxCallback2, -Infinity );

리듀스의 본질은 앞에 값과 다음 값을 가지고 다른 동작을 처리하되, 결과값이 하나란 것이다.

reduce() 작동 방식

[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array) {
  return accumulator + currentValue;
});

객체 배열에서의 값 합산

객체로 이루어진 배열에 들어있는 값을 합산하기 위해서는 반드시 초기값을 주어 각 항목이 여러분의 함수를 거치도록 해야 합니다.

왜냐 acc에는 계속해서 누적된 결과 값이 들어가게 된다.
currValue야 키 값을 통해 값을 건져내면 되지만,
acc는 처음음 키값으로 꺼낼 수 있다 하더라도 두번째 부턴 숫자에서 키를 꺼내는 형식이 되버린다.

2중 배열 펼치기(flatten)

var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  function(accumulator, currentValue) {
    return accumulator.concat(currentValue);
  },
  []
);
// 펼친 결과: [0, 1, 2, 3, 4, 5]

당연히 이것은 초기값을 안 줘도 된다.

var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  function(accumulator, currentValue) {
    return accumulator.concat(currentValue);
  }
);
// 펼친 결과: [0, 1, 2, 3, 4, 5]

객체 내의 값 인스턴스 개수 세기

var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

var countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

allNames라는 객체를 초기값으로 주고 누적된 결과를 여기 반영한다.
해당 객체에 각 name이 존재하면 숫자를 증가, 아니면 1로 초기화 시켜준다.
자주 쓰일만할 것 같다.

속성으로 객체 분류하기

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

특정 속성을 key로 하여 객체를 구분지을 수 있다.
대단하다.

확장 연산자와 초기값을 이용해 객체로 이루어진 배열에 담긴 배열 연결하기

// friends - an array of objects
// where object field "books" - list of favorite books
var friends = [{
  name: 'Anna',
  books: ['Bible', 'Harry Potter'],
  age: 21
}, {
  name: 'Bob',
  books: ['War and peace', 'Romeo and Juliet'],
  age: 26
}, {
  name: 'Alice',
  books: ['The Lord of the Rings', 'The Shining'],
  age: 18
}];

var allbooks = friends.reduce(function(acc, cur) {
  return acc.concat(cur.books)
  // return [...accumulator, ...currentValue.books];
}, []);

// allbooks = [
//   'Bible', 'Harry Potter', 'War and peace',
//   'Romeo and Juliet', 'The Lord of the Rings',
//   'The Shining'
// ]

배열의 중복 제거

let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
let result = arr.sort().reduce((accumulator, current) => {
    const length = accumulator.length
    if (length === 0 || accumulator[length - 1] !== current) {
        accumulator.push(current);
    }
    return accumulator;
}, []);
console.log(result); //[1,2,3,4,5]

물론 이는 new Set()을 이용하면 더 간단해 질 것 같다.

프로미스를 순차적으로 실행하기

function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),Promise.resolve(input)
  );
}

// promise function 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
 return a * 3;
}

// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10)
  .then(console.log);   // 1200

일단 여기까지만 정리해야 겠다..

profile
코딩은 해봐야 아는 것

0개의 댓글