map, filter, reduce

ddullgi·2022년 6월 8일
0
post-thumbnail

본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.


base array

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

map

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

let prices = [];
for (const p of products) {
  prices.push(p.price);
}
console.log(prices); //[ 15000, 20000, 15000, 30000, 25000 ]
  • map함수는 위에 코드와 같이 1대1로 매핑된 값을 수집하는 역할을 한다.

함수형 프로그래밍에서는 함수인자리턴값으로 소통하는 것을 권장한다.

const map = () => {
  let names = [];
  for (const p of products) {
    names.push(p.name);
  }
  //console.log(names);
  return names;
};

위에 코드에서 console.log(names);는 함수의 외부영역에다 직접적인 영향이 미친다. 함수형 프로그래밍에서는 함수인자리턴값으로 소통하는 것을 권장하므로 names를 외부에 영향을 미치는 함수메서드로 전달하는 것이 아니라 결과를 리턴하여 리턴된 값을 개발자가 그 이후에 사용하는 것이 좋다.


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

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

console.log(map((p) => p.name, products)); //[ 15000, 20000, 15000, 30000, 25000 ]
  • map함수를 구현해 보았다.
  • 인자로 함수 f와 이터러블 iter를 받는다.
    • 1대1 매핑할 요소를 함수로 받기때문에 추상화 되었다.
    • 함수를 인자로서 받기 때문에 고차 함수이다.



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

console.log(document.querySelectorAll('*').map(el => el.nodeName));
//document.querySelectorAll(...).map is not a function

console.log(map(el => el.nodeName, document.querySelectorAll('*')));
//['HTML', 'HEAD', 'META', 'TITLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'STYLE', 'IRON-ICONSET-SVG',  …]

const it = document.querySelectorAll('*')[Symbol.iterator]();
console.log(it.next()); //{value: html.focus-outline-visible, done: false}
console.log(it.next()); //{value: head, done: false}
console.log(it.next()); //{value: meta, done: false}
console.log(it.next()); //{value: title, done: false}
  • document.querySelectorAllMap함수(default)가 내부에 구현되어 있지 않다.
  • 하지만 앞서 구현한 map함수(maked)로는 순회가 가능하다.
    • document.querySelectorAll이터러블/이터레이터 프로토콜을 따르기 때문!!
function *gen() {
  yield 2;
  yield 3;
  yield 4;
}
console.log(map((a) => a * a, gen())); //[ 4, 9, 16 ]
  • 즉, 이터러블/이터레이터 프로토콜을 따른다면 거의 모든것들에 map함수(maked)를 쓸 수 있다.

let m = new Map();
m.set("a", 10);
m.set("b", 20);
const it = m[Symbol.iterator]();
console.log(it.next()); //{ value: [ 'a', 10 ], done: false }
console.log(it.next()); //{ value: [ 'b', 20 ], done: false }
console.log(it.next()); //{ value: undefined, done: true }
  • Map(default)도 이터러블/이터레이터 프로토콜을 따른다.
console.log(map(([k, a]) => [k, a * 2], m)); //[ [ 'a', 20 ], [ 'b', 40 ] ]
console.log(new Map(map(([k, a]) => [k, a * 2], m))); //Map(2) { 'a' => 20, 'b' => 40 }
  • Map(default)과 map(maked) 모두 이터러블/이터레이터 프로토콜을 따르기 때문에 Map(default)의 인자로 map(maked)를 넣을 수 있고, 그 반대도 가능하다.


filter

let under20000 = [];
for (const p of products) {
 if (p.price < 20000) under20000.push(p);
}
console.log(...under20000);
//{ name: '반팔티', price: 15000 } { name: '핸트폰케이스', price: 15000 }
  • filter함수는 조건의 맞는 값들을 걸러주는 역할을 합니다.

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

console.log(...filter((p) => p.price < 20000, products));
//{ name: '반팔티', price: 15000 }, { name: '핸트폰케이스', price: 15000 }

console.log(...filter((p) => p.price >= 20000, products));
//{ name: '긴팔티', price: 20000 } { name: '후트티', price: 30000 } { name: '바지', price: 2
5000 }
  • filter함수를 구현해 보았다.
  • 인자로 조건을 걸러줄 함수 f와 이터러블 iter를 받는다.

console.log(filter(n => n % 2, function *() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
}))
//[ 1, 3, 5 ]
  • filter함수 역시 map함수 처럼 이터러블/이터레이터 프로토콜을 따르기 때문에 거의 모든 것들에 사용 가능하다.


reduce

const nums = [1, 2, 3, 4, 5];
let total = 0;
for (const n of nums) {
  total += n;
}
console.log(total); //15
  • reduce함수는 값을 축약해 주는 역할을 한다.
    • 어떤 특정한 값을 계속해서 순회하면서 하나의 값으로 누적해 나갈때 사용한다.

const add = (a, b) => a + b;
  • 위와 같이 a + b를 해주는 add라는 함수가 있다고 할 때, add를 통해 reduce를 구현해 본다면 다음과 같은 형태가 될 것이다.
add(add(add(add(0, 1), 2), 3), 4)
//15
  • 보다시피 재귀 구조로 이루어져 있다는 걸 알 수 있다.
    • 즉, reduce함수의 내부에는 재귀적인 반복 구조가 들어가야된다.

const reduce = (f, acc, iter) => {
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
};
  • reduce함수를 구현해 보았다.
  • 인자로 조건을 걸러줄 함수 f와 누적 값 acc 그리고 이터러블 iter를 받는다.
  • 자바스크립트에서 기본 제공하는 reduceacc를 생략해도 동작을 하기때문에 바꿔야한다.
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;
};

console.log(reduce(add, 0, [1, 2, 3, 4, 5])); //15
console.log(reduce(add, [1, 2, 3, 4, 5])); //15
  • acc의 값의 존재 여부를 판별할 if문을 넣어준다.
    • 인자의 값을 들어온 순서로 받기 때문에 acc의 존재 여부는 3번쩨 인자인 iter의 존재 여부로 판별한다.
      • acc가 없을 경우, 들어온 acciter이다.
  • iter의 값을 받지 않았기 때문에 iteracc(iter)의 이터레이터를 넣어준다.
  • 그 다음 iter.next()(iter 내부의 첫 값)을 acc에 할당 해준다.



map, filter, reduce 중첩 사용해보기

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

console.log(
  reduce(
    add,
    filter(
      (n) => n >= 20000,
      map((p) => p.price, products)
    )
  )
);
//75000
  • 중첩 해서 사용하면 위와 같이 쓸 수 있다.
profile
프론트엔드개발자를 꿈꾸는 예비 개발자

0개의 댓글