본문 바로가기
JavaScript

[FP&ES6+] map, filter, reduce

by _sweep 2021. 12. 1.

함수형 프로그래밍과 JavaScript ES6+ 강의를 듣고 정리한 내용입니다.

 

 

 

*** 함수형 프로그래밍에서는 함수가 인자와 리턴 값을 통해 소통하는 방식을 권장한다.

 

 map

const map = (f, iter) => {
    let res = [];
    for(const a of iter) {
        res.push(f(a))
    }
    return res;
}

 

map 함수는 함수와 이터러블/이터레이터 프로토콜을 따르는 값을 인자로 받는다.

이후 이 값을 순회하며 함수에 따라 처리한다.

즉, 값을 처리하는 방식을 추상화하여 함수를 통해 어떤 값을 어떻게 처리할 것인지를 정한다.

 

const fruits = [
    { name: '사과', price: '1000' },
    { name: '바나나', price: '2000' },
    { name: '딸기', price: '1500' },
    { name: '레몬', price: '1000' },
    { name: '복숭아', price: '3000' },
]

 

위와 같은 값들이 들어있는 fruits라는 배열이 주어졌다고 가정했을 때, 각 과일들의 이름에 접근하는 방법은 다음과 같다.

 

console.log(map(f => f.name, fruits));

// output
// ['사과', '바나나', '딸기', '레몬', '복숭아']

 

map 함수의 인자로 이름을 추출하는 함수와 fruits라는 이터러블/이터레이터 프로토콜을 따르는 값이 들어갔고 fruits의 내부 값을 순회하면서 이름만을 골라 res라는 빈 배열에 저장 후 res를 return, 출력하도록 한 것이다.

 

map 함수는 다음과 같이도 사용할 수 있다.

 

let m = new Map();
m.set('a', 10);
m.set('b', 20);

console.log(
    new Map(
        map(([k, a]) => [k, a * 2], m)
    )
)

// output
// {'a' => 20, 'b' => 40}

 

 

 filter

const filter = (f, iter) => {
    let res = [];
    for(const a of iter) {
        if(f(a)) res.push(a);
    }
}

 

filter는 조건에 따라 값을 걸러내는 역할을 한다.

함수와 이터러블/이터레이터 프로토콜을 따르는 값을 인자로 받아 함수에 선언된 조건에 따라 값을 걸러내는 것이다.

 

const fruits = [
    { name: '사과', price: '1000' },
    { name: '바나나', price: '2000' },
    { name: '딸기', price: '1500' },
    { name: '레몬', price: '1000' },
    { name: '복숭아', price: '3000' },
]

 

다시 이 값들이 주어졌다고 가정했을 때 filter 함수를 통해 과일의 값이 1500 미만인 과일들을 골라낼 수 있다.

 

console.log(...filter(f => f.price < 1500, fruits))

// output
// {name: '사과', price: 1000}
// {name: '레몬', price: 1000}

 

좀 더 응용해보자면 제너레이터 함수를 인자로 받아 다음과 같은 작업도 가능하다.

 

console.log(
    filter(n => n % 2, function* () {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
        yield 5;
    }())
)

// output
// [1, 3, 5]

 

 

reduce

const reduce = (f, acc, iter) => {
    if(!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
    }
    
    for(const a of iter) {
        acc = f(acc, a);
    }
    
    return acc;
}

 

reduce는 값을 축약하는 함수이다.

값을 처리할 function, 초기값인 acc, 값들이 들어있는 iterator를 인자로 받아 주어진 값들을 순회하며 함수에 따른 처리를 한 후 결과값을 acc 안에 축적하여 저장한다.

 

초기값인 acc를 주지 않을 경우 iterator의 첫번째 값이 acc가 되며 나머지 값을 순회하며 function에 따른 처리를 한다.

따라서 acc가 없는 경우 acc의 자리에 iter가 왔으므로 이터러블한 acc를 이터레이터로 만들어 iter 자리에 삽입하고 acc는 이터레이터의 첫번째 값을 naxt()로 꺼내서 갖는다.

이 과정을 거치면서 acc는 첫번째 값을 가지게 되고 이 값을 뺀 나머지를 순회하며 보조함수에 따른 처리를 할 수 있게 된다.

 

const add = (a, b) => a + b;
console.log(reduce(add, 0, [1, 2, 3, 4, 5]));
console.log(reduce(add, [1, 2, 3, 4, 5]));

// output
// 15
// 15

 

다시 fruits라는 값이 주어졌다고 가정했을 때 과일 가격의 총 합을 구하려면 다음과 같이 접근하면 된다.

 

const fruits = [
    { name: '사과', price: '1000' },
    { name: '바나나', price: '2000' },
    { name: '딸기', price: '1500' },
    { name: '레몬', price: '1000' },
    { name: '복숭아', price: '3000' },
]
console.log(
    reduce((sum, f) => sum + f.price, 0, fruits)
)

// output
// 8500

 

 

✏️ map & filter & reduce 활용

위 셋을 종합하여 주어진 과일 중 값이 1500원 미만인 과일들의 가격 총 합을 구할 수 있다.

 

const add = (a, b) => a + b;

console.log(
    reduce(
        add,
        map(f => f.price,
            filter(f => f.price < 1500, fruits)
        )
    )
);

console.log(
    reduce(
        add,
        filter(n => n < 1500,
            map(f => f.price, fruits)
        )
    )
);

// output
// 2000
// 2000

 

첫번째 콘솔 로그의 경우 제일 먼저 filter 함수를 통해 과일들 중에 가격이 1500 미만인 것들을 골라낸 후 map 함수를 통해 그 과일들의 가격을 뽑아낸다. 이후 reduce 함수를 통해 이 가격들을 더해 총합을 얻어낸 것이다.

 

두번째 콘솔 로그의 경우에는 첫번째와는 반대로 map 함수를 통해 과일들의 가격을 먼저 뽑아냈다. 그리고 이 뽑아낸 수들을 가지고 filter 함수를 통해 값이 1500 미만인 것들을 골라낸 후 reduce 함수를 거쳐 총합을 얻어냈다.

 

 

 

 

 

댓글