혼자 적어보는 노트

프로그래머스 데브코스 TIL - Day 9 본문

스터디

프로그래머스 데브코스 TIL - Day 9

jinist 2022. 4. 1. 04:06

 학습 목차

- [DAY 9] VanillaJS를 통한 자바스크립트 기본 역량 강화 (1)

- 함수형 프로그래밍과 ES6+ (3)

 

✅ 새롭게 학습한 부분

- map, filter 계열 함수들이 가지는 결합 법칙

- 지연평가의 장점

- 지연성을 이용하여 다양한 함수 만들기

- 응용

 


 

map, filter계열의 함수가 가지는 결합법칙

어떤 데이터를 사용하든지 보조 함수가 순수 함수라면

아래와 같이 결합 시 결과가 같다.

 

[[mapping, mapping], [filtering, filtering], [mapping, mapping]]
=
[[mapping, filtering, mapping], [mapping, filtering, mapping]]

 

ES6의 기본 규약을 통해 구현하는 지연평가의 장점.

ES6 이후 부터 함수와 함수가 리턴되는 값을 통해서

지연 여부를 확인하고 원하는 시점에 평가 하는 등

자바스크립트와 개발자가 약속된 규약을 가지고 만들어 갈 수 있다.

 

자바스크립트의 고유한, 약속된 값을 통해 구현된 지연성은

다른 라이브러리나 다른 사람들이 만든 함수 등

자바스크립트의 기본 값과 기본 객체를 통해 소통하기 때문에 조합성이 높다.

 

결과를 만드는 함수 reduce, take

reduce, take는 내부의 값을 꺼내서 사용하기 때문에 결과를 만드는 함수라고 볼 수 있다.

map이나 filter같은 함수는 배열이나 이터러블값의 안쪽에 있는 원소들에게 함수들을

합성하는 역할을 하며, 지연성을 가지는 함수이다.

 

reduce는 안쪽의 원소들을 꺼내서 더하는 등 결과를 만드는 함수이다.

take 또한 값을 2개만 yield 하기로 약속을 하여 지연성을 줄 수 있지만

특정 개수의 배열로 축약하고 값을 완성시키는 성질을 가지고 있기 때문에

take를 한 시점에서 연산이 이루어지는 것이 편리하다.

 

함수형 프로그래밍 시 a로부터 b값을 만들 때, a를 받아서 map, filter등을 반복하고

reduce로 최종적인 값을 리턴하겠다는 사고로 접근하자!

 

 

reduce를 사용하여 join 함수 만들기

const join = curry((sep = ",", iter) => reduce((a, b) => `${a}${sep}${b}`, iter));

function* test() {
  yield 10;
  yield 20;
  yield 30;
  yield 40;
}

console.log(join("*", test())); // 10*20*30*40

iterable을 인자를 받아서 reduce를 실행하여 join의 기능을 구현했다.

Array.prototype.join은 배열에서만 사용이 가능하지만 reduce는 이터러블 객체를 모두 순회하고

축약할 수 있기 때문에 더 다형성이 높다고 볼 수 있다.

 

L.entries와 join 사용하기

const obj = { name: "jay", age: 29 };

const join = curry((sep = ",", iter) => reduce((a, b) => `${a}${sep}${b}`, iter));

L.entries = function* (obj) {
  for (const key in obj) yield [key, obj[key]];
};

const info = (obj) =>
  go(
    obj,
    L.entries,
    L.map(([a, b]) => `${a}:${b}`),
    join(", ")
  );

console.log(info(obj)); // name:jay, age:29

여기서 L.entries와 L.map은 지연성을 가지며 join에서 결과를 만든다.

여기서 info는 obj를 받아서 그대로 obj를 전달하기 때문에 pipe로 변경할 수 있다.

const info = pipe(
    L.entries,
    L.map(([a, b]) => `${a}:${b}`),
    join(", ")
  );

 

filter와 take를 사용하여 find함수 만들기

const users = [
  { name: "Jay", age: 29 },
  { name: "Hey", age: 24 },
  { name: "May", age: 22 },
  { name: "Joy", age: 21 },
  { name: "Choi", age: 27 },
  { name: "Roy", age: 25 },
];

const find = (f, iter) => go(
  iter,
  filter(f), // users를 전부 탐색해서 조건에 맞는 user를 모두 가져온다.
  take(1), // filter로 받은 user들 중 첫 번째 user만 가져온다.
  ([a]) => a
);

console.log(find((a) => a.age > 25, users));
// {name: 'Jay', age: 29}

여기서 filter는 user을 전부 탐색해서 조건에 부합하는 리스트를 반환한다.

그리고 take로 1명을 추출하게 되는데 효율적이지 못한 방식이다.

 

L.filter를 사용하면 전부 탐색하지 않고 1명이 조건에 맞는다면 탐색을 멈추고 반환할 수 있다.

const find = (f, iter) => go(
  iter,
  L.filter(f),
  take(1), 
  ([a]) => a
);

take가 filter로 1명을 요청하고 1명을 찾은 후 take로 전달하는 방식이다. 

 

L.map, L.filter로 map과 filter만들기

// 기존의 map 코드

const map = curry((f, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  let cur;
  while (!(cur = iter.next()).done) {
    const a = cur.value;
    res.push(f(a));
  }
  return res;
});
const map = curry((f, iter) =>
  go(
    iter,
    L.map(f), // 평가를 할 수 있는 지연이 준비된 상태
    take(Infinity) // 결과를 만들기 위해 take사용
  )
);

L.map을 사용하여 기존의 map과 동일한 기능을 수행할 수 있다.

여기서 iter, L.map(f)는 L.map(f, iter)로 축약할 수 있으며 curry의 인자로 전달되는 f, iter과 같기 때문에

pipe로 축약할 수 있다.

const map = curry(pipe(L.map, take(Infinity)));

 

 

flatten 함수 구현하기

const isIterable = (a) => a && a[Symbol.iterator];

L.flatten = function* (iter) {
  for (const a of iter) {
    if (isIterable(a)) {
      for (const b of a) yield b;
    } else yield a;
  }
};

var it = L.flatten([1, 2, [3, 4], 5, 6, [7, 8]]);
console.log([...it]); // [1, 2, 3, 4, 5, 6, 7, 8]


const flatten = pipe(L.flatten, take(Infinity));
console.log(flatten([1, 2, [3, 4], 5, 6, [7, 8]]));
// [1, 2, 3, 4, 5, 6, 7, 8]

 

L.map과 L.flatten을 조합하여 flatMap을 만들수도 있다.

L.flatMap = curry(pipe(L.map, L.flatten));
const flatMap = curry(pipe(L.map, flatten));

var it = L.flatMap((a) => a, [1, 2, [3, 4], 5, 6, [7, 8]]);

console.log([...it]); //  [1, 2, 3, 4, 5, 6, 7, 8]
console.log(take(2, flatMap((a) => a, [1, 2, [3, 4], 5, 6, [7, 8]]))); // [1, 2]

응용해서 다른 함수들과 조합하여 결과를 만들어 낼 수 있다.

 

응용하여 데이터 추출하기

let products = [
  { name: "사과",  category: "과일", recommend: [{name: "배", price: 1000}, {name: "오렌지", price: 3000}, {name: "귤", price: 4000}] },
  { name: "포도",  category: "과일", recommend: [{name: "거봉", price: 1400}, {name: "망고", price: 2000}, {name: "오렌지", price: 4000}] },
  { name: "치킨",  category: "음식", recommend: [{name: "피자", price: 10000}, {name: "백숙", price: 2000}, {name: "간장", price: 4000}] },
  { name: "라면",  category: "음식", recommend: [{name: "우동", price: 2200}, {name: "짬뽕", price: 5000}, {name: "컵라면", price: 800}] },
]

go(products,
  L.filter(p=> p.category==="과일"),
  L.flatMap(p=> p.recommend),
  L.map((p)=>p.price),
  take(Infinity),
  reduce((a,b)=>a+b),
  console.log
) // 카테고리가 과일인 상품의 recommend 가격 모두 더하기

go(products,
  L.map(p=> p.recommend),
  L.flatten,
  L.filter(p=> p.price>3000),
  L.map(p=> p.name),
  take(2),
  console.log
) // recommend중에 3000원 이상인 상품 2개 이름

 

 

이터러블 중심의 프로그래밍을 이용해서

여러가지의 함수를 만들고 지연평가를 하는 함수를 사용하여

효율성을 높이고 여러 함수를 조합해서 다양한 결과물을 만들 수 있다.

 

 


✍ 느낀 점

 

오늘은 자바스크립트에 대한 문제 해설편이였기때문에 비교적 강의를 금방 끝냈다.

오늘 계획한 함수형 프로그래밍의 강의를 모두 끝내고 추가적으로 다른 공부를 하려고 했지만..

함수형 프로그래밍 파트에서 코드를 따라 쳐보고 있는데 중간에 놓친 것인지

코드에 오류가 생겨서 원인을 찾느라 시간을 많이 사용했다..ㅠㅠ

원인은 pipe함수 때문이였는데 저장을 잘못했는지 매개변수부분의 입력을 잘못했다.

잘못 적었는데도 불구하고 초반에 동작을 하는 바람에 잘못 작성한지 모르고 계속 사용했고

이 부분에 문제가 있을거라고 생각을 못하고 엉뚱한 곳에서 문제를 찾으려 했다.

해당 함수 자체의 원리에 대해 확실하게 이해를 못해서 추적하는데 더 어려웠던 것 같다..

이해에 대한 필요성을 더욱 더 느끼게 되었고 아닐 것 같은 곳도 의심을 해보자!라는 깨달음을 얻었다.

Comments