고차함수 map, filter, reduce에 대해 정리하는 포스팅입니다.
함수형 프로그래밍 시리즈 내용으로 계속 이어서 내용이 진행되므로 처음 부터 포스팅을 확인해주세요.
map()은 배열 각 요소에 대하여 주어진 함수를 수행한 결과를 모아 새로운 배열을 반환하는 메서드이다.
3개의 매개변수를 가진다.
'use strict'
// 사용 예시
const numbers = [1, 2, 3];
const numbersMap = numbers.map((value, index, array) => {
console.log(value, index, array);
return value * 2;
});
console.log(numbersMap);
// 테스트 케이스
const products = [
{name :'반팔티', price: 15000},
{name :'긴팔티', price: 20000},
{name :'핸드폰케이스', price: 15000},
{name :'후드티', price: 30000},
{name :'바지', price: 25000},
];
// 사용자 정의 map
const map = (f, iter) => {
const response = [];
for(let value of iter) {
response.push(f(value));
}
return response;
}
console.log(map(p => p.name, products));
console.log(map(p => p.price, products));
map의 고차함수 성질과 관련하여 활용이 유용한 다형성에 대하여 알아보자.
// 배열은 map 함수가 잘 동작.
[1,2,3].map(a => a +1);
map은 ES6 문법이 생기며 좀 더 복잡한 구조를 가지게 되었다.(함수형 자바스크립트, 이터러블/이터레이터) document.querySelectorAll처럼, 배열이 아닌 이터러블 프로토콜을 따르는 구조는 map함수 사용시undefined가 나타나게 된다.
위와 같은 경우 map을 직접 사용자 정의(위 예시 코드 활용)를 통하여 이터러블 프로토콜을 따르는 여러 함수들에 대하여 원하는 값 출력이 가능해진다.
// querySelectorAll는 배열을 기반으로 동작하지 않기에 map함수가 존재하지 않는다.
// 하지만 querySelectorAll는 이터러블 프로토콜을 따른다.
map(el => el.nodeName, document.querySelectorAll('*'));
// ["HTML", "HEAD", "META", "TITLE", "META", "META", "META", "META", "SCRIPT", "LINK", "SCRIPT", "STYLE", "BODY", "SCRIPT", "PRE", "BR", "PRE", "PRE", "BR", "PRE"]
// 이터레이터를 통해 next함수로 다음 값들을 꺼낸다.
const it = document.querySelectorAll('*')[Symbol.iterator]();
it.next(); // {value: html, done: false}
it.next(); // {value: head, done: false}
it.next(); // {value: script, done: false}
it.next(); // {value: script, done: false}
it.next(); // {value: body, done: false}
// 제너레이터를 통한 map 사용
function *gen() {
yield 2;
if (false) yield 3;
yield 4;
}
console.log(map(a => a * a, gen()));
// 키, 값쌍의 Map을 활용한 map 사용
// Map도 이터러블 프로토콜을 따른다.
const m = new Map();
m.set('a', 10);
m.set('b', 10);
const l = m[Symbol.iterator]();
console.log(l.next());
console.log(l.next());
console.log(l.next());
// 기존 Map 객체에 대해 구조분해를 활용한 새로운 객체 생성
console.log(new Map(map(([k, a]) => [k, a * 2], m)));
filter()는 배열 각 요소에 대하여 주어진 함수의 결과값이 true인 요소들만 모아 새로운 배열로 반환하는 메서드이다.
map()이 내부 함수의 리턴 값이 다양한 타입이 가능했다면, filter()는 오직 boolean 타입만 반환한다. 리턴 값이 true인 경우에만 배열을 추가하기 때문에 중복 제거처럼 조건에 맞는 특정 요소들만 새 배열에 넣고 싶은 경우에 사용하기 적합하다.
3개의 매개변수를 가지며, index와 array는 생략이 가능하다.
// filter 사용 예제
const fruits = ['Apple', 'Banana', 'Lemon'];
const fruitsFilter = fruits.filter((value, index, array) => {
console.log(value, index, array);
return value.length > 5;
});
console.log(fruitsFilter);
filter 역시 map과 마찬가지로 다형성 있는 프로그래밍이 가능하다.
'use strict'
// 테스트 케이스
const products = [
{name :'반팔티', price: 15000},
{name :'긴팔티', price: 20000},
{name :'핸드폰케이스', price: 15000},
{name :'후드티', price: 30000},
{name :'바지', price: 25000},
];
// 사용자 정의 filter 구현
const filter = (f, iter) => {
const response = [];
for(const i of iter) {
if(f(i)) response.push(i);
}
return response;
}
console.log(...filter(p => p.price < 20000, products));
console.log(...filter(p => p.price >= 20000, products));
reduce()는 배열의 각 요소에 대하여 reducer 함수를 실행하고, map과 filter와 달리 배열이 아닌 하나의 결과값을 반환한다.
4개의 매개변수를 가지면 currentIndex와 array는 생략이 가능하다.
// reduce 사용 예제
const numbers = [1,2,3,4];
const numbersSum = numbers.reduce((accumulator, currentValue, currentIndex, array) => {
console.log(accumulator, currentValue, currentIndex, array);
return accumulator + currentValue;
});
console.log(numbersSum);
사용자 정의를 통한 reduce 함수의 장점은 함수의 교체가 가능하여 다양한 로직의 조합성을 실현할 수 있다.
'use strict';
// 테스트 케이스
const products = [
{name :'반팔티', price: 15000},
{name :'긴팔티', price: 20000},
{name :'핸드폰케이스', price: 15000},
{name :'후드티', price: 30000},
{name :'바지', price: 25000},
];
// reduce 사용자 정의
const reduce = (f, acc, iter) => {
if(!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for(const i of iter) {
acc = f(acc, i);
}
return acc;
}
const add = (a, b) => a + b;
// 초기값 지정한 경우의 reduce
console.log(reduce(add, 0, [1, 2, 3, 4, 5]));
// 초기값 지정하지 않은 경우의 reduce
console.log(reduce(add, [1, 2, 3, 4, 5]));
// reduce 응용 예제
// 배열뿐 아니라 객체 배열의 조작도 인자로 넘어가는 f 를 어떻게 구현하느냐에 따라 가능해집니다.
console.log(
reduce(
(total_price, products) => total_price += products.price,
0,
products
));
// 테스트 케이스
const products = [
{name :'반팔티', price: 15000},
{name :'긴팔티', price: 20000},
{name :'핸드폰케이스', price: 15000},
{name :'후드티', price: 30000},
{name :'바지', price: 25000},
];
const add = (a, b) => a + b;
// map, filter, reduce의 중첩 활용
console.log(
reduce(
add,
map(p => p.price,
filter(p => p.price < 20000, products))));
console.log(
reduce(
add,
filter(n => n >= 20000,
map(p => p.price, products))));