adjacentPairs는 인접한 n 개의 요소를 묶어서 순회하는 유틸리티 함수이다. 이때 isClosed가 true이면 배열의 시작과 끝도 연결돼 있다고 간주하고, 마지막 원소까지 순회한다.
function adjacentPairs<T>(
itr: Iterable<T>,
n: number,
isClosed?: boolean,
): IterableIterator<T[]>
예를 들어서 [0, 1, 2, 3, 4] 배열에서 n = 3 인 경우, 각각 다음과 같은 동작을 한다.
isClosed === false
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
isClosed === true
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 0]
[4, 0, 1]
Generator 문법을 쓰면 다음과 같이 매우 간단하게 구현할 수 있다.
export function* adjacentPairs<T>(
itr: Iterable<T>,
n: number,
isClosed?: boolean,
) {
const first = new Array(n - 1) as T[];
const buffer = new Array(n) as T[];
let count = 0;
for (const value of itr) {
pull(buffer, value);
if (++count >= n) {
yield [...buffer];
} else if (count < n) {
first[count - 1] = value;
}
}
if (count >= n && isClosed) {
for (const value of first) {
pull(buffer, value);
yield [...buffer];
}
}
}
function pull<T>(values: T[], newValue: T) {
values.shift();
values.push(newValue);
}
Iterator는 좀 귀찮지만 다음과 같이 수행할 수 있다.
export function adjacentPairs<T>(
itr: Iterable<T>,
n: number,
isClosed?: boolean,
): IterableIterator<T[]> {
const buffer = new Array<T>(n);
const it = itr[Symbol.iterator]();
let res = it.next();
// n개를 불러오고 시작
for (let i = 0; i < n; ++i) {
if (res.done) {
return {
[Symbol.iterator]() {
return this;
},
next() {
return {
value: undefined,
done: true,
};
},
};
}
buffer[i] = res.value;
res = it.next();
}
const first = buffer.slice(0, n - 1);
let isEnd = false;
let ringCount = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
if (isEnd) {
return {
value: undefined,
done: true,
};
}
const ret = buffer.slice();
if (res.done) {
if (isClosed) {
if (ringCount < n - 1) {
pull(buffer, first[ringCount++]);
} else {
isEnd = true;
}
} else {
isEnd = true;
}
} else {
pull(buffer, res.value);
res = it.next();
}
return {
value: ret,
done: false,
};
},
};
}
const N = 10000;
const huge = Array.from(new Array(N), () => 0);
for (const i of adjacentPairs(huge, n, true)) {
i;
}
함수 정의부는 모두 setup에서 수행
n | Iterator (ops/s) | Generator (ops/s) | Slow Rate (%) |
---|---|---|---|
2 | 3800 ± 1.41% | 2100 ± 1.44% | 46.08 |
3 | 3700 ± 2.05% | 2100 ± 0.87% | 44.46 |
50 | 2700 ± 2.32% | 967 ± 2.37% | 63.97 |
100 | 1900 ± 4.3% | 485 ± 6.64% | 74.03 |
실무에서 특히 제너레이터를 많이 쓰는 로직에 빌드하는데 얼마나 시간이 걸리는지 측정하였다.
#1 | #2 | #3 | #4 | #5 | 평균(ms) | |
---|---|---|---|---|---|---|
Iterator (ms) | 17749.52 | 17031.43 | 17047.06 | 17355.85 | 17008.69 | 17328.51 |
Generator (ms) | 29249.87 | 29583.87 | 28810.99 | 29398.45 | 29237.50 | 29256.13 |
제너레이터는 성능에 있어 매우 좋지 않다.