바로 계산과 지연 계산

관련 내용

iterator와 for of 의 관계
함수형 프로그래밍 구현
range / 느긋한 range + take

총 정리 코드

const go = (...list) => reduce((a, f) => f(a), list);

const curry = (f) => (a, ..._) =>
  _.length ? f(a, ..._) : (..._) => f(a, ..._);

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

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 range = (l) => {
  let i = -1;
  let res = [];
  while (++i < l) {
    res.push(i);
  }
  return res;
};

range(4);

//L관련
const L = {};

L.range = function* (l) {
  let i = -1;
  while (++i < l) {
    yield i;
  }
};

L.map = curry(function* (f, iter) {
  for (const a of iter) yield f(a);
});

L.filter = curry(function* (f, iter) {
  for (const a of iter) if (f(a)) yield a;
});

const take = curry((l, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(a);
    if (res.length == l) return res;
  }
  return res;
});

const takeAll = take(Infinity);

go(
  range(10),
  map((n) => n + 10),
  filter((n) => n % 2),
  take(2),
  console.log
);

go(
  L.range(10),
  L.map((n) => n + 10),
  L.filter((n) => n % 2),
  take(2),
  console.log
);

빠른 계산(제너리에터 사용X)과 느린계산(제너리에터 사용O)

range , map , filter를 쓴 빠른계산과 L.range, L.map , L.filter를 쓴 느린계산은 진행과정에서도 차이가 난다.

빠른계산은 range -> map -> filter -> take 순으로 배열의 진행을 처리한다.

느린계산은 take -> filter -> map -> range -> map ->filter ->take의 반복을 처리하며 진행한다.

어떤 것이 더 좋을까?

만약 배열이 엄청 크다면 빠른 계산은 모든 배열을 만들고 해당 배열을 처리해야한다.
하지만, 느린 계산은 해당하는 값마다의 처리를 진행해서 배열이 다 만들어지지 않아도 => 즉, 원하는 요구에 만족하면 해당 내용을 먼저 종료 시키는 효율성에서 앞선다.

효율성 테스트

  • console.time과 console.timeEnd를 이용해 효율성을 비교한다.
console.time();
go(
  range(10000000),
  map((n) => n + 10),
  filter((n) => n % 2),
  take(2),
  console.log
);

console.timeEnd();
console.time();

go(
  L.range(10000000),
  L.map((n) => n + 10),
  L.filter((n) => n % 2),
  take(2),
  console.log
);
console.timeEnd();
  • 결과
[ 11, 13 ]
default: 1.503s
[ 11, 13 ]
default: 0.228ms

배열이 클 수록 더욱 차이가 크다.

이런코드라면? 지연과 바로 계산의 콜라보

let users = [
  {
    name: "a",
    age: 21,
    family: [
      { name: "a1", age: 53 },
      { name: "a2", age: 47 },
      { name: "a3", age: 16 },
      { name: "a4", age: 15 },
    ],
  },
  {
    name: "b",
    age: 21,
    family: [
      { name: "b1", age: 58 },
      { name: "b2", age: 51 },
      { name: "b3", age: 19 },
      { name: "b4", age: 22 },
    ],
  },
  {
    name: "c",
    age: 21,
    family: [
      { name: "c1", age: 64 },
      { name: "c2", age: 62 },
    ],
  },
  {
    name: "d",
    age: 21,
    family: [
      { name: "d1", age: 42 },
      { name: "d2", age: 42 },
      { name: "d3", age: 11 },
      { name: "d4", age: 7 },
    ],
  },
];

const curry = (f) => (a, ..._) =>
  _.length ? f(a, ..._) : (..._) => f(a, ..._);

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

const go = (...list) => reduce((a, f) => f(a), list);
const pipe = (...fs) => (a) => go(a, ...fs);

let L = {};

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

L.map = curry(function* (f, iter) {
  console.log(iter, "L.map Iter");
  for (const a of iter) {
    console.log(a, "L.map a");
    yield f(a);
  }
});

L.map1 = curry(function* (f, iter) {
  console.log(iter, "L.map1 Iter");
  for (const a of iter) {
    console.log(a, "L.map1 a");
    yield f(a);
  }
});

L.filter = curry(function* (f, iter) {
  for (const a of iter)
    if (f(a)) {
      console.log(a, "L.Filter a");
      yield a;
    }
});

const filter = curry((f, iter) => {
  console.log(iter, "filter Iter");
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  console.log(res, "Filter");
  return res;
});

const take = curry((l, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(a);
    if (res.length == l) return res;
  }
  return res;
});

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

go(
  users,
  L.map((u) => u.family),
  L.flatten,
  L.filter((u) => u.age < 20),
  L.map1((u) => u.age),
  take(3),
  reduce(add),
  console.log
);

go(
  users,
  L.map((u) => u.family),
  L.flatten,
  filter((u) => u.age < 20),
  L.map1((u) => u.age),
  take(3),
  reduce(add),
  console.log
);

첫번째 go는 L이 연결되어있고
두번째 go는 L 사이에 filter가 있다.

아마 이러지 않을 까 싶다. 즉, filter는 모든 a b c d 를 순회하지만
L.filter는 필요한 부분만 하나씩 필요할때마다 순회하여 찾아내는 것 같다.

좋은 웹페이지 즐겨찾기