함수형 프로그래밍 - map, filter, reduce

MyeonghoonNam·2021년 8월 10일
2

함수형 프로그래밍

목록 보기
2/10

고차함수 map, filter, reduce에 대해 정리하는 포스팅입니다.

함수형 프로그래밍 시리즈 내용으로 계속 이어서 내용이 진행되므로 처음 부터 포스팅을 확인해주세요.

map

  • map()은 배열 각 요소에 대하여 주어진 함수를 수행한 결과를 모아 새로운 배열을 반환하는 메서드이다.

  • 3개의 매개변수를 가진다.

    • value : 현재 요소
    • index : 요소의 인덱스
    • array : map()을 호출한 원본 배열
'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);

사용자 정의 map

// 테스트 케이스
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의 고차함수 성질과 관련하여 활용이 유용한 다형성에 대하여 알아보자.


  • 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을 다양하게 활용할 수 있다.
// 제너레이터를 통한 map 사용
function *gen() {
  yield 2;
  if (false) yield 3;
  yield 4;
}

console.log(map(a => a * a, gen()));

  • Map() 객체 역시 이터러블 프로토콜을 따르므로 활용가능하다.
// 키, 값쌍의 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

  • filter()는 배열 각 요소에 대하여 주어진 함수의 결과값이 true인 요소들만 모아 새로운 배열로 반환하는 메서드이다.

  • map()이 내부 함수의 리턴 값이 다양한 타입이 가능했다면, filter()는 오직 boolean 타입만 반환한다. 리턴 값이 true인 경우에만 배열을 추가하기 때문에 중복 제거처럼 조건에 맞는 특정 요소들만 새 배열에 넣고 싶은 경우에 사용하기 적합하다.

  • 3개의 매개변수를 가지며, index와 array는 생략이 가능하다.

    • value : 현재 요소
    • index : 요소의 인덱스
    • array : filter()를 호출한 원본 배열
// 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

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

  • reduce()는 배열의 각 요소에 대하여 reducer 함수를 실행하고, map과 filter와 달리 배열이 아닌 하나의 결과값을 반환한다.

  • 4개의 매개변수를 가지면 currentIndex와 array는 생략이 가능하다.

    • accumulator : 현재까지의 결과 값을 저장하는 변수로 초기값을 지정한 경우에는 초기값 부터 시작하며 초기값 지정을 안하는 경우 배열의 첫 번째 요소부터 시작.
    • currentValue : 현재 요소
    • 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 사용자 정의

사용자 정의를 통한 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))));


참고자료

profile
꾸준히 성장하는 개발자를 목표로 합니다.

0개의 댓글