중첩된 배열을 풀어서 리턴하는 flatten을 만들어보자
const data = [[1, 2], 3, 4, [5, 6], [7, 8, 9]];
const L = {};
const isIter = (e) => {
return e && e[Symbol.iterator];
}
L.flatten = function *(iter) {
for (const a of iter){
if(isIter(a)){
// 이터러블인경우 a를 돌면서 또 빼내기
for(const b of a){
yield b;
}
} else {
yield a;
}
}
}
우선 이터러블을 돌면서 이터러블의 요소가 다시 이터러블인지 확인하면서 요소를 하나씩 꺼내는 제너레이터를 만들었다.
L.flatten은 지연 평가가 되는 함수로, 연산이 필요할때 값이 생성되게 된다.
즉시 평가하는 flatten은 take연산을 통해 만들 수 있다.
const takeAll = take(Infinity);
const flatten = pipe(L.flatten, takeAll);
takeAll 함수는 배열의 마지막 요소까지 모두 가져오는 함수이다.
L.flatten를 통해 지연적으로 동작하지만, 마지막 요소까지 모두 평가하는 takeAll함수를 pipe로 연속적으로 작성하여 flatten을 만들 수 있다.
2차원이아닌 3차원이상의 배열이 있는 경우 모두 펼치기 위한 deepFlat을 만들어보자.
L.deepFlat = function* f(iter) {
for (const a of iter) {
if (isIter(a)) yield* f(a);
else yield a;
}
};
iter을 재귀적으로 호출하면서 요소를 꺼내올 수 있다.
flatMap은 map과 flatten을 함께 하는 함수이다.
const data = [[1, 2], [3, 4], [5, 6, 7]];
위 데이터가 있다. 중첩된 배열을 풀기위해서는 아래와 같이 할 수 있다.
go(
data,
L.map(a => a),
L.flatten,
takeAll,
log
) // [1,2,3,4,5,6,7] 출력
즉, L.map과 L.flatten을 함께 써서 사용할 수 있고 아래와 같이 사용할 수 있다.
L.flatMap = curry(pipe(L.map, L.flatten))
go(
data,
L.flatMap(a => a),
takeAll,
log
) // [1,2,3,4,5,6,7] 출력
간략하게 아래의 순서로 작동된다.
- reduce로 작동되는 go 함수로 중첩 배열인 data와 L.flatMap이 작동된다.
- L.flatMap의 인자로 data가 들어간다.
- pipe함수로 인해 L.flatMap의 L.map이 먼저 실행된다.
- L.map은 지연적으로 평가하며 요소를 하나씩 꺼낼 준비를 한다.
([1,2] <- 1. 꺼낼 준비)
([3,4] <- 2.꺼낼 준비)
([5,6,7] <- 3.꺼낼 준비)- L.flatten은 위 배열을 펼칠 준비를 한다.
- takeAll로 인해 배열의 모든 요소를 log에 전달한다. 이 부분에서 값이 평가된다.
go(data,
L.flatMap(map(a => a * a)),
takeAll,
log
) // [1, 4, 9, 16, 25, 36, 49] 출력
flatMap의 인자를 즉시 평가하는 함수로 변경하면 위와 같이 요소의 값을 변경할 수 있다.
또는 아래와 같이 응용해 볼 수 있다.
go(
[1, 2, 3],
L.flatMap(L.range),
takeAll,
log,
) // [0, 0, 1, 0, 1, 2] 출력
L.range는 숫자를 인자로 받아서 해당 숫자만큼 0부터 증가하는 배열을 만든다.
1이 들어왔을때 [0]
2가 들어왔을때 [0,1]
3이 들어왔을때 [0,1,2] 가되며
이는 flatten으로 펼쳐져서 [0, 0, 1, 0, 1, 2] 이런 결과를 만들어낸다.
지연 평가 부분 중 가장 이해하기 어려운 부분이었고 잘 쓰기까지는 많은 훈련이 필요할 것 같다.ㅜㅜ