Frontend/JS

[JavaScript] L.flatten, flatten, L.deepFlat 함수 만들기

턴태 2023. 5. 9. 22:43

L.flatten 함수

flatten이라는 함수를 만들고자 한다. 해당 함수는 임의로 배열이 섞여 있는 배열을 하나의 배열로 전환해주는 함수다. 또한, 지연 평가를 적용할 수 있는 함수다.

 

예를 들어, 아래와 같은 배열이 있다고 한다면

[[1, 2], 3, 4, [5, 6], [7, 8, 9]];

이를 하나의 배열로 처리해 아래와 같이 만들어 줄 수 있다.

[1, 2, 3, 4, 5, 6, 7, 8, 9]

이제 본격적으로 함수를 생성한다.

const isIterable = a => a && a[Symole.iterator];

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

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

// {value: 1, done: false}

이처럼 L.flatten은 배열을 원소로 가지는 배열을 받아서 하나의 배열로 만들어 주는 함수이며, 이터레이터를 반환한다.

 

L.flatten이 있으면 즉시 평가하는 flatten도 쉽게 만들 수 있다.

더보기

이전에 만들었던 함수들

const curry = f =>
  (a, ...fs) => fs.length
  ? f(a, ...fs)
  : (...fs) => f(a, ...fs)
    
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 = (...fs) => reduce((a, f) => f(a), ...fs);

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

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

const takeAll = take(Infinity);
const flatten = pipe(L.flatten, takeAll);

또한, 이터러블한 이터레이터를 반환하므로 아래와 같이 쓸 수도 있다.

console.log(take(6, L.flatten([[1, 2], 3, 4, [5, 6], [7, 8, 9]]));

yield *

yield * 을 활용해서 아래와 같이 코드를 바꿀 수 있다. yield *iterablefor (const val of iterable) yield val; 과 같다.

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

L.deepFlat

더 깊은 iterable을 모두 펼치고자 한다면 아래와 같이 L.deepFlat을 구현할 수 있다.

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

console.log([...L.deepFlat([1, [2, [3, 4], [[5]]]])]);
// [1, 2, 3, 4, 5];

이부분은 이전에 배웠던 함수의 연속이라 크게 와닿지 않았다. 그런데, yield *이나 deepFlat 부분은 새로 알 수 있어서 좋았다. yield * 패턴은 특히 깊은 배열을 풀어서 쓰는 데 유용해서 좋았다. 애스터리스크가 이터러블한 이터레이터를 반환하는 것이기 때문에 이렇게 반환하는 것 같다. 그리고 deepFlat의 경우 재귀적으로 작용하는 것으로 파악된다. 그래서 배열의 depth가 아무리 길다고 하더라도 모든 값들을 뽑아낼 수 있는 것 같다. 새로 배울 수 있는 점이라서 좋았다. 앞으로 제너레이터와 비동기적인 작동이 이어질 텐데 어떻게 효율적으로 코드를 작성할 수 있을지 기대된다.

 

출처: 인프런 함수형 프로그래밍과 JavaScript ES6+