이 강의는 Inflearn 유인동 님의 강의 함수형 프로그래밍과 Javacsript ES6+ 를 수강하고 작성한 글입니다.
🐳 함수형 프로그래밍의 기초 Map, Filter, Reduce 그 중에서도 Reduce
함수형 프로그래밍은 여러 함수를 조합해 값을 다루는 패러다임이다.
그 중에서도 함수형 프로그래밍의 근간이 되는 세 가지 함수를 꼽으라면 map, filter, reduce가 있을 것이다.
map과 filter는 함수형 프로그래밍을 주로 사용하지 않더라도 자주 사용해 보았을 것이다.
map(f,iter)은 이터러블 객체를 순회하면서 iter.next() 의 값에 f를 적용해 새로운 iterable 객체를 리턴한다.
filter(f,iter)는 이터러블 객체를 순회하면 f(iter.nex())를 만족하는 값만 걸러 새로운 iterabler 객체를 리턴한다.
하지만 reduce는 뭔지 잘 모를 수도 있다.
오늘은 세 가지 근간 함수 중에 reduce에 대해 알아보자 !
🐳 기본 Reduce 함수
함수형 프로그래밍에서는 map과 filter가 함수를 적용해 새로운 iterable 객체를 리턴했다면
reduce는 iterable을 평가해 값을 축약하는 함수이다.
코드와 함께 본다면 더 이해하기 쉬울 것이다.
// f는 적용할 함수, acc는 입력받는 초기값이자 누적값, iter 는 iterable 객체
const reduce = (f,acc,iter) => {
for (const a of iter) {
acc = f(acc, a);
}
return acc
}
위의 코드를 살펴보자
iter 객체를 for...of 반복문으로 순회하면서
acc에 f(acc,a)를 누적하는 것이다.
이렇게만 보면 이해가 잘 가지 않을 수 있으니 위의 reduce를 사용한 예제를 만들어보자.
두 수를 더하는 함수은 add 함수를 만들어 reduce에 적용해 보자 !
const add = (a, b) => a + b;
// range함수는 (start,end,step)으로 start이상 end 미만의 수를 step 단위로 생성하는 이터러블 객체다.
console.log(reduce(add, 0, range(1,10,1)));
( range 함수가 궁금하시다면 여기를 클릭해 주세요. )
위의 코드를 살펴보면
acc : 초기값은 0
iter : range(1,10,1)로 1~9까지의 값을 리턴할 것이다.
그렇다면 결과는 어떻게 될까 ?
답은 45일 것이다.
그 과정을 reduce 함수의 과정과 함께 살펴보자
const reduce = (add,0,range(1,10,1)) => {
for (const num of range(1,10,1)) {
acc = add(acc, num);
}
return acc
}
reduce 함수에 add, 0, range을 인자로 전달한 코드를 살펴보자.
range(1,10,1)을 순회하면서 acc 값을 add(acc,num) 으로 갱신하고 있다.
처음 num = 1이 될 것이고 acc = 0 이다. add를 적용한 결과 값은 1이 될 것이다.
그다음 num = 2 이고 acc = 1 이다. add를 적용한 결과 값은 3이 될 것이다.
그다음 num = 3 이고 acc = 3 이다. add를 적용한 결과 값은 6이 될 것이다.
그다음 num = 4 이고 acc = 6 이다. add를 적용한 결과 값은 10이 될 것이다.
이 과정을 num = 9 일 때까지 적용한 결과 reduce 값은 45가 되는 것이다.
이처럼 reduce함수는 함수, 초기값(누적값), 이터러블 객체를 인자로 받아
이터러블 객체를 순회하면서서 f ( acc, iter.next()) 를 적용해 누적값을 갱신한 후
누적값을 리턴하는 것이다.
이러한 특성 때문에 reduce 함수는 map, filter와는 다르게 함수를 축약해 값을 만드는 함수라고 하는 것이다.
🐳 Reduce 함수 고도화
위의 reduce 함수는 인자를 세 개를 받았다. 함수, 초기값, iterable 객체
그런데 여기서 초기값을 주지 않고 iterable의 첫번째 값을 초기값으로 사용할 수는 없을까 ?
위의 reduce 함수를 초기값이 없는 경우 iterable의 첫 번째 값을 초기값으로 사용하도록 수정해 봅시다.
const reduce = (f,acc,iter) => {
// iter가 없을 때는 acc가 iterable이라고 가정
// iter를 이터레이터로 변환 후 acc에 iterator의 첫번째 값을 꺼내 대입
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc
}
위의 코드의 !iter 부분 부터 보면
iter가 없는 경우, 즉 인자가 2개만 들어온 경우에는 ( 이 때 acc는 iterable하다고 가정합시다 )
iter 를 iterable한 객체인 acc의 Symbol.iterator 를 실행해 이터레이터 객체로 만들어 주고
그 iter의 첫번째 값인 iter.next().value를 acc의 값에 대입해 준다.
이렇게 코드를 구현해 주면 reduce 함수에 인자로 함수와 이터러블 객체만 주면
초기값을 이터러블 객체의 첫번째 값으로 설정한 후에 reduce함수를 자동으로 실행해 준다.
🐳 Reduce 함수 테스트 코드 작성
우리가 예상했던 결과들이 잘 도출 되는지 테스트코드를 작성해 봅시다 !
const { reduce, add, range } = require('./fp');
describe('reduce function', () => {
it('iterable을 순회하며 값을 축약한다.', () => {
const sum = reduce(add, 0, range(1, 10, 1));
expect(sum).toBe(45);
});
it('초기값이 주어지지 않은 경우 iterable객체의 첫 번째 값을 초기값으로 사용한다.', () => {
const sum = reduce(add, [1,2,3,4,5]);
expect(sum).toBe(15);
});
});
초기값이 있는 경우와 없는 경우의 두 가지 테스트 코드를 모두 테스트 해봅시다.
처음 테스트 코드는 iterable한 객체인 range를 입력 받아 초기값 0 부터 1~9의 수를 더한 값을 출력합니다.
두 번째 테스트 코드는 초기값을 입력받지 않고 reduce함수를 실행합니다.
이 경우 위의 코드에서 의도한 것처럼 1이 첫 번째 값이 되고 2,3,4,5를 더해 15의 값이 나와야 합니다.
테스트 코드를 실행해 봅시다.
모두 예상한 결과값이 나오는 것을 확인할 수 있습니다.
🐳 요약
1. reduce함수는 값을 축약하는 함수이다.
2. 초기 값이 없는 경우 iterable 객체의 첫 번째 값을 초기값으로 사용하게 할 수 있다.
'Node.js > Javascript' 카테고리의 다른 글
[함수형프로그래밍] 2. 이터러블 이터레이터 프로토콜 근데 이제 range 함수 예제를 곁들인 (27) | 2024.03.26 |
---|---|
[함수형프로그래밍] 0. 함수형 프로그래밍이란 ? (0) | 2024.03.14 |
[함수형프로그래밍] 1. 일급함수란 ? (0) | 2024.03.12 |
[Javascript] 날짜 구하기 / 문자열 앞에 문자 더하기 (0) | 2023.06.04 |