요구조건으로 이런것이 왔다고 쳐봅시다.
서비스 개발한 서비스의 사용자의 데이터에서 포인트의 누적을 자신의 가족 수를 곱하고 그 값이 1000점 이상인 사람들 중에서 해당 포인트의 제곱근이 소수점을 버리고 홀 수 인 사람 중에 두사람의 마지막 계산된 포인트를 뽑아달라는 요청이 왔다.
(말도 안되는 시나리오지만 아 그렇구나 하고 넘어가자.)
일단 저 2명을 뽑기 위해서 take라는 함수를 구현해봅시다.
const take = ( count, iter ) => {
const res = []
for( const item of iter ) {
res.push( item )
if( res.length === count ) return res
}
return res
}
잘 동작 합니다.
고객 정보가 없기 때문에 적당히 고객정보를 구현 할 수 있도록 range도 구현을 해봅시다.
const range = ( limit ) => {
const res = []
let count = 0
while( count < limit ) {
res.push( count++ )
}
return res
}
이제 샘플로 사용 할 고객 정보를 임의로 만들 수 있도록 함 수 하나를 만들겠습니다. 위 시나리오상 가족수랑 포인트만 있으면 되겠네요.
const genMember = () => {
return {
family: Math.ceil(Math.random() * 8),
point: Math.ceil(Math.random() * 8000)
}
}
적당히 만들었습니다. 그러면 이제 1000만명을 만들어서 위의 로직을 돌려보도록 하죠.
저의 PC가 못버티는 관계로... 10만명으로 하겠습니다.
const members = go( range( 1000000 ), map( genMember ) )
간단하게 이처럼 처리하면 됩니다. 이제 위의 로직을 적당히 처리하면 됩니다.
const calcPointList = go(
members,
map( member => {
const { family, point } = member
return {
...member,
calPoint: family * point
}
} ),
filter( ( { calPoint } ) => calPoint >= 1000 ),
map( ( { calPoint } ) => Math.floor( Math.sqrt( calPoint ) ) ),
filter( calPoint => calPoint % 2 ),
take(2)
)
위 처럼 적당히 짜보았고 take 함수를 아래처럼 curry를 적용 해두었다.
const take = curry( ( count, iter ) => {
const res = []
for( const item of iter ) {
res.push( item )
if( res.length === count ) return res
}
return res
} )
2991.68994140625 ms
2784.303955078125 ms
3068.023193359375 ms
시간을 확인 해보았더니 2.5 ~ 3초(랜덤으로 멤버를 만들어서 그런거 아닌가 싶다.)까지 걸리는 로직이다. 단 두명을 구하는 로직인데 너무 오래 걸리지 않은가? 이를 개선을 하려면 어떻게 해야 할까.
지금 로직은 모든 멤버들의 값을 변경 하고 필터링 하고 값을 변경 하고 필터를 한 다음에 그 중에 두명을 뽑고 있다. 이럴것이 아니라. 해당조건에 만족하는 두명이 나올때 까지만 위의 절차를 진행 할 수 없을까?
LazyMap, LazyFilter를 구현해보자.
하기 전에 generator function을 알아볼 필요가 있다.
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i){
yield i;
yield* anotherGenerator(i);
yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
[출처] https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/function*
generator function은 위와 같이 리턴 값으로 Iterator 객체를 리턴 한다. 우리가 만든 함수들은 interable, iterator protocol을 잘 따르고 있기 때문에 위의 generator function을 잘 적용 시킬 수 있다.
const lazyMap = curry( function*( func, iter ) {
for( const item of iter ) {
yield func( item )
}
} )
const lazyFilter = curry( function*( func, iter ) {
for( const item of iter ) {
if( func( item ) ) {
yield item
}
}
} )
간단 하게 위처럼 함수를 작성한 후
const calcPointList = go(
members,
lazyMap( member => {
const { family, point } = member
return {
...member,
calPoint: family * point
}
} ),
lazyFilter( ( { calPoint } ) => calPoint >= 1000 ),
lazyMap( ( { calPoint } ) => Math.floor( Math.sqrt( calPoint ) ) ),
lazyFilter( calPoint => calPoint % 2 ),
take(2)
)
이 코드를 실행 시켜 보자.
0.220947265625 ms
0.2099609375 ms
0.340087890625 ms
이처럼 속도가 9000 ~ 10000배 정도 차이가 나는 것을 볼 수 있다.
댓글
댓글 쓰기