ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JS] Map, Filter, Reduce
    기초/자바스크립트 2022. 12. 17. 02:53

    인프런 강의 정리

    함수형 프로그래밍과 Javascript ES6+

     

    *custom Map 함수

    /**
     f : 맵 속성을 위임할 함수
     iter : 이터러블을 반환하는 제너레이터
     function : 이터러블을 받아 맵을 생성하는 함수
    **/
    const map = (f, iter) => {
      let res = [];
      for (const a of iter) {
        res.push(f(a));
      }
      return res;
    };

     

    *원하는 value만 맵으로 생성

    console.log(map(p => p.name, products));
    console.log(map(p => p.price, products));
    
    /**
    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);
    **/

     

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

    1. 기존 이터러블을 이용한 맵 객체 생성

    /**
     document.querySelectorAll('*')[Symbol.iterator](); //Array Iterator {}
    **/
    console.log([1, 2, 3].map(a => a + 1)); // [2, 3, 4]
    console.log(map(el => el.nodeName, document.querySelectorAll('*'))); // [ tag list ... ]
    
    function* gen() {
      yield 2;
      if (false) yield 3;
      yield 4;
    }
    
    console.log(map(a => a * a, gen())); // [4, 16]

     

    2. 기존 맵을 이용한 맵 객체 생성

    - 맵의 값을 변경하여 사용할 수 있다.

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

     

    *Filter

    조건에 맞는 Value만 맵으로 생성

    /**
     f : 조건
     iter : 이터러블
     function : 조건에 맞는 이터러블 집합을 맵으로 생성
    **/
    const filter = (f, iter) => {
      let res = [];
      for (const a of iter) {
        if (f(a)) res.push(a);
      }
      return res;
    };

     

    const products = [
      {name: '짜장', price: 10000},
      {name: '짬뽕', price: 12000},
      {name: '볶음밥', price: 15000},
      {name: '탕수육', price: 25000}
    ];
    
    // 원하는 결과만 가져오기
    console.log(...filter(p => p.price < 20000, products));
    // {name: '짜장', price: 10000} {name: '짬뽕', price: 12000} {name: '볶음밥', price: 15000}
    
    console.log(...filter(p => p.price >= 20000, products));
    // {name: '탕수육', price: 25000}
    
    console.log(filter(n => n % 2, [1, 2, 3, 4])); // [1, 3]
    
    // 제너레이터를 바로 사용할 수 있다.
    console.log(filter(n => n % 2, function* () {
      yield 1;
      yield 2;
      yield 3;
    }())); // [1, 3]

     

    *reduce

    집합을 요약

    /** 
    f : 연산함수
    acc : 집합의 초기값
    iter : 집합
    function : 집합을 연산함수에 따라 요약하는 함수
    **/
    const reduce = (f, acc, iter) => {
      //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(add(add(add(add(add(0, 1), 2), 3), 4), 5)); // 15
    console.log(reduce(add, [1, 2, 3, 4, 5])); // 15

     

    -사용2

    -이터러블 함수들을 다양하게 사용

    const products = [
      {name: '짜장', price: 10000},
      {name: '짬뽕', price: 12000},
      {name: '볶음밥', price: 15000},
      {name: '탕수육', price: 25000}
    ];
    
    const add = (a, b) => a + b;
    
    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;
    };
    
    /**
     reduce, add, map > filter 
    **/
    console.log(
      reduce(
        add,
        map(p => p.price,
          filter(p => p.price < 20000, products)))); //37000
    
    /**
     reduce, add, filter > map
    **/
    console.log(
      reduce(
        add,
        filter(n => n >= 20000,
          map(p => p.price, products)))); //25000

     

    읽기 좋은 코드로 변경하는 방법

    1. 값과 함수를 반환하는 함수 만들기

    /**
     (a, f) : 값과 함수로 이뤄진 파라매터 리스트
     args : 파라매터 리스트 
     go : 순차적으로 함수를 처리해 값을 반환하는 함수
     
     첫번째 값 >> 두번째 함수의 파라매터
     두번째 함수의 결과값 >> 세번째 함수의 파라매터 ...
    **/
    const go = (...args) => reduce((a, f) => f(a), args);
    
    /**
     (...as) : 아규먼트 리스트
     f : 첫번째 함수
     fs : 함수리스트
     pipe : 내부적으로 go를 사용하는 함수 리스트
    **/
    
    const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
    /**
    사용2 함수
    
    console.log(
      reduce(
        add,
        map(p => p.price,
          filter(p => p.price < 20000, products)))); //37000
          
      **/
     
     /**
      위의 함수를 더 간편하게 보기위해 go함수를 사용한 리펙토링 
     **/
    go(
        products,
        products => filter(p => p.price < 20000, products),
        products => map(p => p.price, products),
        prices => reduce(add, prices),
    console.log);

     

    #curry

    /**
     f = (a, ..._) 함수를 받아서 함수를 리턴
     f(a, ..._) : 리턴된 함수가 2개 이상일 때 즉시 실행
     (..._) => f(a, ..._) : 리턴된 함수가 2개 미만일 때 인자를 받도록 대기하는 함수를 리턴하고
                            이 후에 받은 함수와 합쳐서 실행
    **/
    
    const curry = f =>
      (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

     

    - filter, map, reduce함수 에 curry함수를 적용

    -기존 map, reduce, filter

    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) => {
      //iter가 생략된 경우
      if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
      }
      
      for (const a of iter) {
        acc = f(acc, a);
      }
      return acc;
    };

     

    - curry가 적용된 함수

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

     

    -적용된 함수

     /**
      위의 함수를 더 간편하게 보기위해 go함수를 사용한 리펙토링 
    go(
        products,
        products => filter(p => p.price < 20000, products),
        products => map(p => p.price, products),
        prices => reduce(add, prices),
    console.log);
     **/
     
    go(
      products,
      filter(p => p.price < 20000),
      map(p => p.price),
      reduce(add),
      console.log);

     

    -pipe 함수를 사용해 공통부분 추출 및 함수 의미부여

    const total_price = pipe(
        map(p => p.price),
        reduce(add)); // (...as) => go(f(...as), ...fs)
    
    const base_total_price = predi => pipe(
        filter(predi),
        total_price);
    
    /**
     console.log(base_total_price(p => p.price < 20000)(products))
    **/
    go(
        products,
        base_total_price(p => p.price < 20000), // (...as) => go(f(...as), ...fs)
        console.log); // 37000

    '기초 > 자바스크립트' 카테고리의 다른 글

    [JS] 제너레이터  (0) 2022.12.17
    [JS] 일급 함수와 고차함수  (0) 2022.12.14
    GridSatck (Test.html)  (0) 2022.03.16
    [JS] 모자이크 처리  (2) 2022.03.04
    [Javascript] OpenLayers + GeoServer + PostGIS + VWORLD + WFS,WMS  (6) 2020.11.11

    댓글

Designed by Tistory.