본문 바로가기

Web[웹]/ES6+ 함수형 언어

[JS: ES6+] 이터러블 프로토콜을 따른 map, filter, reduce

 

#1 map

 

이터러블 프로토콜을 따른 map

 

간단하게 먼저, map 함수는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다.

 

var array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

 

이제, 이터러블 프로토콜을 따른 map함수를 알아볼건데요, 먼저 map을 사용하지 않은 예제를 봅시다.

 

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

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, 40000, 30000, 25000]


다음은 위와같은 동작을 하는 map함수로 쓴 예제이다. map함수는 직접적으로 값을 수집하는 것이 아니라 추상적으로 값을 수집한다.

 

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

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.price, products));
//[15000, 20000, 40000, 30000, 25000]

 

다음은 map함수를 활용해 제너레이터와 함께 써봤다.

 

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

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

 

결론적으로 map 함수는 array뿐만 아니라 이터러블 프로토콜을 따르는 많은 함수들을 다 사용 할 수 있다. 사실상 모든 것들을 map을 사용할 수 있다.

 


 

#2 filter

 

이터러블 프로토콜을 따른 filter

 

간단하게 먼저 filter는 조건에 맞는 내용만 리스트에 남겨두고 나머지 요소는 삭제하는 것이다.

 

아래코드는 filter를 사용하지 않은 코드이다. [ 가격이 3만원 이상, 미만인 제품 뽑기 ]

 

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

let under30000 = [];
for(const p of products) {
   if(p.price < 30000) under30000.push(p);
}
console.log(...under30000);
// {name: "반팔티", price: 15000}
// {name: "긴팔티", price: 20000}
// {name: "반바지", price: 25000}

let over30000 = [];
for(const p of products) {
   if(p.price >= 30000) over30000.push(p);
}
console.log(...over30000);
// {name: "후드티", price: 40000}
// {name: "긴바지", price: 30000}

 

위 코드를 filter로 재구성 해보자.

 

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

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 < 30000, products));
// {name: "반팔티", price: 15000}
// {name: "긴팔티", price: 20000}
// {name: "반바지", price: 25000}
console.log(...filter(p => p.price >= 30000, products));
// {name: "후드티", price: 40000}
// {name: "긴바지", price: 30000}

 

filter 역시 중복된 요소를 제거해서 이러터블 프로토콜을 사용함을 알 수 있다.

 

다음은 filter함수를 활용해 제너라이트와 함께 써봤다.

 

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

 


 

#3 reduce

 

이터러블 프로토콜을 따른 filter

 

간단하게 먼저 reduce는 하나의 다른 값으로 축약하는 함수이다. 아래의 예제는 reduce가 동작하는 코드를 구성해본 것이다.

 

const nums = [1, 2, 3, 4, 5];
let total = 0;
for (const n of nums) {
   total = total + n;
}
console.log(total); // 15

 

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

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

console.log(reduce(add, 0, [1, 2, 3, 4, 5])); // 15
console.log(reduce(add, [1, 2, 3, 4, 5])); // 15 (!iter이더라도 같은 동작을 함)

 

이제 products의 모든 금액들을 더하는 것을 reduce만으로 작성해보자.

 

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

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(total_price, product) => total_price + product.price, 0, products));
//130000

 


 

#4 map+filter+reduce 중첩

 

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

 

이 챕터에선 위에서 이터러블을 따른 함수들을 전부 같이 사용할 것이다. 아래의 코드를 보자.

[ 3만원 미만의 제품의 금액의 합을 나타내기]

 

/*fx.js*/
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;
};

 

/*src="fx.js"*/

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

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

//console.log(map(p => p.price, products));
//console.log(map(p => p.price, filter(p => p.price < 30000, products)));
console.log(reduce(
               add,
               map(p => p.price, 
               filter(p => p.price < 30000, products)))); // 60000
               //3만원 미만의 제품을 filter로 뽑고, 그 값들을 map을 통해 뽑아낸 후, 
               //해당하는 모든값을 reduce로 축약해서 나타냄

 

위의 코드를 작성할 때는 reduce부터 filter까지 작성 하고 해석할 때는, filter부터 reduce 순으로 이해하는 것이 편하다.