reduce는 덧셈에만 쓰일 수 있는 것이 아니었다!

조민호·2023년 1월 26일
0


지금까지 JS에서 reduce는 오로지 덧셈을 구현할때만 사용했고 더 나아가 봐야 사칙연산 정도로만 사용 했었다. 그리고 실제로 reduce는 이런 용도로만 사용되는 줄 알고 있었다

그러던 도중 우연찮게 우테코 5기 프리코스를 진행했고 , 여기서 jest로 테스트코드를 작성하는데 fn()모킹을 메소드 체이닝으로 구현하는 것을 reduce로 매우 간편하게 사용하는 것을 보고 매우 놀랐던 기억이 있다

reduce함수는 단지 덧셈만 사용할 수 있는 것이 아니라 , 메소드체이닝을 비롯해 map , filter , sort 등등의 수많은 배열 메소드들을 구현할 수 있었다

그래서 실제로 reduce메소드를 바탕으로 (덧셈을 제외하고)할 수 있는 것들에 대해서 알아보았다


reduce의 기본 동작은 초깃값을 지정하고 에서 배열을 순회하며 누적값을 따로 생성하며

로직을 수행하는 것입니다

  • 초깃값을 지정할수 있으므로 다양한 자료형을 사용할 수 있습니다 배열 , 객체 , 메소드 등등이 들어갈 수 있습니다
  • 또한 배열을 순회하며 , 누적값이라는 것을 매번 가지고 있습니다 그러므로 초깃값에 객체를 넣어서 메소드 체이닝도 할 수 있고 , 객체 뿐만 아니라 배열을 넣어서 객체와 배열에 대해서 새로운 값을 리턴해주는 map , filter , sort 등등의 연산이 가능합니다


메소드 체이닝

	class School {

    student = 10;
    name = 'winterClass';

    enroll(count) {
      this.student += count;
      return this;
    }

    printStatus() {
      console.log(`student count is ${this.student}`);
    }

  }

  const school = new School();

  let numbers = [2, 7, 3]; // enroll할 학생 수

 // school.enroll(2).enroll(7).enroll(3)
	numbers.reduce((acc, value) => {
    return acc.enroll(value);
  }, school);

  school.printStatus();

2명 , 7명 , 3명에 대해서 enroll()체이닝을 진행합니다

  1. 일반적인 방식은
    • 메소드를 직접 작성합니다
      school.enroll(2).enroll(7).enroll(3)
    • 그리고 반드시 enroll()메소드는 return this를 해줘야 메소드마다 school객체를 리턴해서 체이닝이 가능한 것입니다
  1. reduce를 활용하는 방식은
    • reduce를 통해 초깃값을 school객체로 두고 여기다가 numbers 배열의 인자를 enroll()메소드의 인자로 넣어줍니다
    • reduce함수의 리턴문의 방식은 2가지가 있습니다
      • enroll()한 것을 리턴했다면
        return acc.enroll(value);
        enroll()메소드는 return this를 해줘야 합니다 enroll()한 결과값을 누산기에 누적 하는 것이므로 반드시 enroll()은 school객체를 리턴해야 합니다
      • enroll()하고 누적값 부분을 리턴했다면
        acc.enroll(value);
        return acc;
        enroll()메소드는 return this를 안 해줘도 됩니다 enroll()메소드의 결과값을 누산기에 누적하는 게 아니라 현재 acc인 school객체를 따로 누산기에 누적하기 때문입니다


map , filter

reduce는 map의 기능또한 구현할 수 있습니다

	// map

	const oneTwoThree = [1, 2, 3];

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

초깃값을 배열로 만들고, 배열에 값들을 push하면 map과 같아집니다.

  // reduce

	const oneTwoThree = [1, 2, 3];	

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

이를 응용해서 조건부로 push를 하면 filter와 같아집니다.

아래의 코드는 1,2,3중에서 1보다 큰 값 중에서 홀짝을 판별하는 코드입니다

	const oneTwoThree = [1, 2, 3];

  let resultForReduce = oneTwoThree.reduce((acc, cur) => {
    if (cur > 1) {
      acc.push(cur % 2 ? '홀수' : '짝수');
    }
    return acc;
  }, []);
  console.log(resultForReduce); // [ '짝수', '홀수' ]

눈치 챘듯이 , reduce로 구현하는 map과 filter는 거의 차이가 없습니다
초깃값에서 배열을 순회하며 로직을 수행하는 것이기 때문에 연산을 걸어주든 , 조건을 걸어주든 모두 자유롭게 가능하기 때문입니다

로직을 수행했는데 동일한 배열 요소에 매핑이 된거면 map이 수행된거고
로직을 수행했는데 배열을 필터링 한거면 filter가 수행된 것입니다

그래서 sort, every, some, find, findIndex, includes도 다 reduce로 구현 가능하게 됩니다

💡 그렇지만 보다 직관적이고 , 보다 사용처에 맞는 빠른 연산을 위해 각각의 메소드를 사용하는 것이 권장됩니다



비동기 처리

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

초깃값을 Promise.resolve()로 한 후에, return된 프로미스에 then을 붙여 다음

누적값으로 넘기면 , 프로미스가 순차적으로 실행됨을 보장할 수 있습니다.

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



flatten (배열 납작하게 만들기)

2차원 이상의 배열들을 flatten 할 때 배열을 순회하면서

concat (이어 붙이는) 로직을 reduce 를 활용해서 구현할 수 있습니다.

	const data = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
  ];
  const flatArrayReducer = (accumulator, value, index, array) => {
    return accumulator.concat(value);
  };
  const flattenedData = data.reduce(flatArrayReducer, []);
  // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]



flattenMap

배열을 순회하면서 배열의 값으로 들어있는 object 의 key 존재여부를 확인하고,

unique 한 “cast 를 key 로 갖는 배열의 값들”을 최종적으로 return 하는 로직을 구현할 수 있습니다.

	const input = [
    {
      title: '슈퍼맨',
      year: '2005',
      cast: ['장동건', '권상우', '이동욱', '차승원'],
    },
    {
      title: '스타워즈',
      year: '2013',
      cast: ['차승원', '신해균', '장동건', '김수현'],
    },
    {
      title: '고질라',
      year: '1997',
      cast: [],
    },
  ];
  const flatMapReducer = (accumulator, value, index, array) => {
    const key = 'cast';
    if (value.hasOwnProperty(key) && Array.isArray(value[key])) {
      value[key].forEach((val) => {
        if (accumulator.indexOf(val) === -1) {
          accumulator.push(val);
        }
      });
    }
    return accumulator;
  };
  const flattenCastArray = input.reduce(flatMapReducer, []);

  console.log(flattenCastArray)
  // ['장동건', '권상우', '이동욱', '차승원', '신해균', '김수현']



배열 요소의 중복 횟수를 객체로 표현

let votes = ['kim', 'hong', 'lee', 'hong', 'lee', 'lee', 'hong'];

  let reducer = function (accumulator, value, index, array) {
    if (accumulator.hasOwnProperty(value)) {
      accumulator[value] = accumulator[value] + 1;
    } else {
      accumulator[value] = 1;
    }
    return accumulator;
  };

  let result = votes.reduce(reducer, {});
  console.log(result); // { kim: 1, hong: 3, lee: 3 }

(사실 아래 코드가 더 간단하긴 한듯)

	let votes = ['kim', 'hong', 'lee', 'hong', 'lee', 'lee', 'hong'];

  let result = {};

  for (let i of votes) {
    result[i] === undefined ? (result[i] = 1) : (result[i] += 1);
  }
  console.log(result);

출처 :

[JS #3] 자바스크립트 배열 메서드 3, reduce 100% 활용법 (feat. egghead.io)

ZeroCho Blog

profile
웰시코기발바닥

0개의 댓글