map, filter, reduce

이효범·2022년 4월 28일
0
post-thumbnail

map, filter, reduce 함수는 실용적으로 많이 쓰이는 함수이다.

map

const products = [
    {name: '반팔티', price: 15000},
    {name: '긴팔티', price: 20000},
    {name: '핸드폰케이스', price: 15000},
    {name: '후드티', price: 30000},
    {name: '바지', price: 25000}
];

let names = [];
for (const p of products) {
 names.push(p.name); 
}
log(names); // ["반팔티", "긴팔티", "핸드폰케이스", "후드티", "바지"]

let prices = [];
for (const p of products) {
 prices.push(p.price); 
}
log(prices); //  [15000, 20000, 15000, 30000, 25000]

# map
// map 로직 이해하기
const map = (f, iter) => { // map 함수가 받는 값은 이터러블을 따른다. 
  // 위 for...of 로직과 거의 유사하지만
  // 함수형 프로그래밍에서는 함수가 인자와 리턴값으로만 소통하는 것을 권장한다.
  let res = [];
  for (const a of products) {
    res.push(f(a));  // 추상화 및 위임
  }
  return res;
};

console.log(map(p => p.name, products)); // ["반팔티", "긴팔티", "핸드폰케이스", "후드티", "바지"]

함수형 프로그래밍에서 map은 보조 함수와 배열 혹은 이터러블을 통해 진행되며,
전달된 배열안에 있는 혹은 이터러블 안에 있는 값에 1대1로 맵핑되는 어떠한 값을 내가 수집하겠다고 보조 함수를 전달하는 식으로 사용하게 된다.

또한 map은 앞서 얘기했었던 고차 함수이기도 하다.
함수를 값으로 다루면서 내가 원하는 시점에 내부에서 인자를 적용하는 그러한 함수인 것이다.

이터러블 프로토콜을 따른 map의 다형성

map 함수는 이터러블 프로토콜을 따르고 있기 때문에 다형성이 굉장히 높다.

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


log([1, 2, 3].map(a => a + 1));

log(document.querySelectorAll('*')); 
log(map(el => el.nodeName, document.querySelectorAll('*')));

  // const it = document.querySelectorAll('*')[Symbol.iterator]();
  // log(it.next());
  // log(it.next());
  // log(it.next());
  // log(it.next());
  // log(it.next());

  // 위에서 우리가 작성해 놓은 map 함수는 이터러블 프로토콜을 따르는 for...of 문을 사용했기 때문에 
// 배열 뿐만 아니라 이터러블 프로토콜을 따르는 많은 함수들을 사용할 수 있다.
// 즉 굉장히 다형성이 높다.

  function* gen() {  
    yield 2;
    if (false) yield 3;
    yield 4;
  }

  log(map(a => a * a, gen()));  // [4, 16]

  let m = new Map();
  m.set('a', 10);
  m.set('b', 20);
  log(new Map(map(([k, a]) => [k, a * 2], m)));
  // Map(2) {'a' => 20, 'b' => 40}

  const iter = m[Symbol.iterator]();
  log(iter.next());  // { value: ["a", 10], done: false }
  log(iter.next());  // { value: ["b", 20], done: false }
  log(iter.next());  // { value: undefined, done: true } 

ECMA Script에서는 이터러블 프로토콜을 따르고 있는 함수들을 계속해서 만들었으며 또한 앞으로도 만들어질 것이기 때문에,
이터러블 프로토콜을 따르는 함수들을 사용한다는 것은 앞으로 다른 많은 헬퍼 함수들과의 조합성이 좋아진다는 이야기이도 하다.

따라서 이터러블 프로토콜을 따르는 함수는
프로토 타입 기반으로 혹은 클래스 기반으로 어떠한 뿌리를 가진, 어떠한 카테고리 안에 있는 어떤 함수만을 사용할 수 있는 기법보다 훨씬 더 유연하고 다형성이 높다고 할 수 있다.

filter

filter 함수 역시 이터러블 프로토콜을 따른다.

// let under20000 = [];
// for (const p of products) {
//   if (p.price < 20000) under20000.push(p);
// }
// log(...under20000);

const filter = (f, iter) => {
    let res = [];
    for (const a of iter) {
      if (f(a)) res.push(a);
    }
    return res;
};
log(...filter(p => p.price < 20000, products));

// let over20000 = [];
// for (const p of products) {
//   if (p.price >= 20000) over20000.push(p);
// }
// log(...over20000);

log(...filter(p => p.price >= 20000, products));

log(filter(n => n % 2, [1, 2, 3, 4]));  // [1, 3] 

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

reduce

reduce 함수는 이터러블 값들을 하나의 다른 값으로 추격해나가는 함수이다.
이는 특정한 이터러블을 순회하면서 하나의 값으로 누적해 나가는 형태이다.
코드를 통해 살펴보도록 한다.

const nums = [1, 2, 3, 4, 5];

let total = 0;
for (const n of nums) {
  total = total + n;
}
log(total);  // 15


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;
};

const add = (a, b) => a + b; // reduce는 보조함수에게 어떻게 추격할 지에 대해 완전히 위임한다. 

log(reduce(add, 0, [1, 2, 3, 4, 5]));
// 15

log(add(add(add(add(add(0, 1), 2), 3), 4), 5));
// 15

log(reduce(add, [1, 2, 3, 4, 5]));
// log(reduce(add, 1, [2, 3, 4, 5])); 자바스크립트 내장 메소드 reduce는 이와 같이 내부적으로 동작하게 된다. 
// 15
const products = [
    {name: '반팔티', price: 15000},
    {name: '긴팔티', price: 20000},
    {name: '핸드폰케이스', price: 15000},
    {name: '후드티', price: 30000},
    {name: '바지', price: 25000}
];

log(
    reduce(
      (total_price, product) => total_price + product.price,
      0,
      products));
// 105000

reduce 함수 역시 보조 함수를 통해 안쪽에 있는 값의 다형성을 잘 지원하며 이터러블을 통해 외부의 값에 대한 다형성도 잘 지원한다.

map + filter + reduce 중첩 사용과 함수형 사고

함수형 프로그래밍에서는 밑의 코드처럼 함수들을 중첩하고 연속적으로 실행하면서 원하는 하나의 값으로 평가를 처리하는 식으로 코딩을 하게 된다.

const log = console.log;

const products = [
  {name: '반팔티', price: 15000},
  {name: '긴팔티', price: 20000},
  {name: '핸드폰케이스', price: 15000},
  {name: '후드티', price: 30000},
  {name: '바지', price: 25000}
];

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

log(
  reduce(
   add,
   map(p => p.price,
      filter(p => p.price < 20000, products))));

log(
  reduce(
    add,
    filter(n => n >= 20000,
      map(p => p.price, products))));


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

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

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;
};
profile
I'm on Wave, I'm on the Vibe.

0개의 댓글