이번 시간의 스프린트인 고차함수 코플릿과 커스텀 lodash 구현인 Underbar에 대해 포스팅한다.
compose 함수
function compose (func1, func2) {
return val => func1(func2(val))
}
compose 함수에 reduce를 응용하면 pipe 패턴이 된다.
function composeReduced (...funcArgs) {
return initVal => funcArgs.reduce((acc, cur) => cur(acc), initVal)
}
특정 배열에서 최솟값, 최대값을 찾을때 reduce 활용하면 편리하다. 리듀스의 콜백에서 리턴해주는 값이 다음 이터레이션의 acc가 되는걸 잘 기억하자.
2차원 배열을 flatten 할때도 reduce 활용한다.
function getLongestElement (arr) {
// TODO: 여기에 코드를 작성합니다.
return arr.reduce((acc, cur) => {
if (cur.length > acc.length) {
return cur
}
return acc
}, '')
}
function flatArr (arr) {
return arr.reduce((acc, cur) => [...acc, ...cur], []);
}
이번 스프린트의 하이라이트.
기본 제공되는 arr관련 메서드를 직접 구현해보는 스프린트.
재밌었다.
_.slice = function (arr, start, end) {
let _start = start || 0;
let _end = end;
if (start < 0) _start = Math.max(0, arr.length + start);
if (end < 0) _end = Math.max(0, arr.length + end);
if (_end === undefined || _end > arr.length) _end = arr.length;
let result = [];
for (let i = _start; i < _end; i++) {
result.push(arr[i]);
}
return result;
}
_.take = function (arr, n) {
let _n = n;
if (_n === undefined || _n < 0) _n = 0;
return _.slice(arr, 0, _n);
}
_.drop = function (arr, n) {
let _n = n;
if (_n === undefined || _n < 0) _n = 0;
return _.slice(arr, _n, arr.length)
}
_.last = function (arr, n) {
let _n = n;
if (_n === undefined || _n < 0) _n = 1
return _slice(arr, arr.length - _n)
}
_.each = function (collection, iteratee) {
if (Array.isArray(collection)) {
for (let i = 0; i < collection.length; i++) {
iteratee(collection[i], i, collection);
}
} else {
for (let key in collection) {
iteratee(collection[key], key, collection);
}
}
};
_.indexof = function (arr, target) {
let result = -1
_.each(arr, fuction (item, index) {
if (item === target && result == -1) {
result = index;
}
});
return result;
}
// _.filter, _.reject는 생략
_uniq = function (arr) {
let result = [];
_.each(arr, item => {
_indexof(arr, item) === -1 && result.push(item)
});
return result;
};
_map = function (arr, iteratee) => {
let result = [];
_.each(arr, item => {
result.push(iteratee(item));
});
};
// 끝판대장 reduce 구현하기
// 안보고 다시 구현해보자. FAIL.. 😞...
// 여러 번 보고 안보고 구현할 수 있도록 외워보자 🤔🤔🤔
_reduce = function (arr, iteratee, initVal) => {
let acc = initVal;
_.each(arr, function (item, idx, src) {
if (initVal === undefined && idx === 0) {
acc = item;
} else {
acc = iteratee(acc, item, idx, src);
}
});
return result;
}
다 풀었는데, 마지막 sort를 구현 못해서 레퍼런스를 봄.. 😞
sort의 콜백은 반환값이 1, 0, -1 중 하나가 되어야 한다
'use strict'
// _.once는 callback 함수를 한 번만 호출하는 '함수'를 리턴합니다.
// _.once가 리턴하는 함수를 여러 번 호출해도 callback 함수는 한 번 이상 호출되지 않습니다.
_.once = function (func) {
let isCalled = false;
let result = '';
return function (...args) {
if (!isCalled) {
result = func(...args);
isCalled = !isCalled;
}
return result;
};
};
_.delay = function (func, wait, ...args) {
return setTimeout(func, wait, ...args);
_.includes = function (arr, target) {
try {
_.each(arr, item => {
if (item === target) {
throw new Error('found!');
};
});
} catch {
return true;
}
return false;
};
_.every = function (arr, iteratee) {
if (arr.length === 0) return true;
if (iteratee === undefined) {
return _.reduce(arr, (acc, cur) => !!cur && acc, true);
}
return _.reduce(arr, (acc, cur) => !!iteratee(cur) && acc, true);
};
_.some = function (arr, iteratee) {
if (arr.length === 0) return false;
if (iteratee === undefined) {
return _.reduce(arr, (acc, cur) => !!cur || acc, false);
}
return _.reduce(arr, (acc, cur) => !!iteratee(cur) || acc, false);
};
_.extend = function (obj, ...args) {
_.each(args, elem => {
for (let key in elem) {
obj[key] = elem[key];
}
});
return obj;
};
// _.defaults는 _.extend와 비슷하게 동작하지만, 이미 존재하는 속성(key)을 덮어쓰지 않습니다.
_.defaults = function (obj, ...args) {
_.each(args, elem => {
for (let key in elem) {
if (!obj.hasOwnProperty(key)) {
obj[key] = elem[key];
}
}
});
return obj;
};
// _.zip은 여러 개의 배열을 입력받아, 같은 index의 요소들을 묶어 배열로 만듭니다.
// 각 index 마다 하나의 배열을 만들고, 최종적으로 이 배열들을 요소로 갖는 배열을 리턴합니다.
// _.zip의 입력으로 전달되는 배열이 수는 정해져 있지 않고, 각 배열의 길이는 다를 수 있습니다.
// 최종적으로 리턴되는 배열의 각 요소의 길이는 입력으로 전달되는 배열 중 가장 '긴' 배열의 길이로 통일됩니다.
// 특정 index에 요소가 없는 경우, undefined를 사용합니다.
// 반복문(for, while)을 사용할 수 있습니다.
// _.each, _.reduce, _.pluck 중 하나 이상을 반드시 사용하여야 합니다.
// 아래 예제를 참고하시기 바랍니다.
// const arr1 = ['a','b','c'];
// const arr2 = [1,2];
// const result = _.zip(arr1, arr2)
// console.log(result); // --> [['a',1], ['b',2], ['c', undefined]]
_.zip = function (...args) {
const maxLength = _.reduce(args, (acc, cur) => {
if (cur.length > acc) {
return cur.length;
}
return acc;
}, 0);
return _.reduce(args, (acc, cur) => {
// console.log({ acc, cur, idx, maxLength });
for (let i = 0; i < maxLength; i++) {
if (acc[i] === undefined) {
acc[i] = [];
}
acc[i].push(cur[i]);
}
return acc;
}, []);
// TODO: 여기에 코드를 작성합니다.
};
// _.zipStrict은 _.zip과 비슷하게 동작하지만,
// 최종적으로 리턴되는 배열의 각 요소의 길이는 입력으로 전달되는 배열 중 가장 '짧은' 배열의 길이로 통일됩니다.
// 그 외 조건은 앞서 _.zip과 동일합니다.
_.zipStrict = function (...args) {
const minLength = _.reduce(args, (acc, cur) => {
if (cur.length < acc) {
return cur.length;
}
return acc;
}, Infinity);
return _.reduce(args, (acc, cur) => {
// console.log({ acc, cur, idx, maxLength });
for (let i = 0; i < minLength; i++) {
if (acc[i] === undefined) {
acc[i] = [];
}
acc[i].push(cur[i]);
}
return acc;
}, []);
// TODO: 여기에 코드를 작성합니다.
};
// _.intersection은 여러 개의 배열을 입력받아, 교집합 배열을 리턴합니다.
// 교집합 배열은 모든 배열에 공통으로 등장하는 요소들만을 요소로 갖는 배열입니다.
// 교집합 배열의 요소들은 첫 번째 입력인 배열을 기준으로 합니다.
// 교집합이 없는 경우 빈 배열을 리턴합니다.
// 아래 예제를 참고하시기 바랍니다.
_.intersection = function (...args) {
return _.reduce(args, (acc, cur) => {
return _.filter(acc, elem => _.indexOf(cur, elem) !== -1);
});
};
// _.difference는 여러 개의 배열을 입력받아, 차집합 배열을 리턴합니다.
_.difference = function (...args) {
return _.reduce(args, (acc, cur) => {
return _.filter(acc, elem => _.indexOf(cur, elem) === -1);
});
};
// _.sortBy는 배열의 각 요소에 함수 transform을 적용하여 얻은 결과를 기준으로 정렬합니다.
// transform이 전달되지 않은 경우, 배열의 요소 값 자체에 대한 비교 연산자의 결과를 따릅니다.
// 예를 들어, number 타입간 비교는 대소 비교이고 string 타입간 비교는 사전식(lexical) 비교입니다.
// 세 번째 인자인 order는 정렬의 방향을 나타냅니다. 생략되거나 1을 입력받은 경우 오름차순, -1을 입력받은 경우 내림차순으로 정렬합니다.
// 아래 예제를 참고하시기 바랍니다.
// const people = [
// { id: 1, age: 27 },
// { id: 2, age: 24 },
// { id: 3, age: 26 },
// ];
// function byAge(obj) {
// return obj.age;
// };
// const result = _.sortBy(people, byAge);
// console.log(result); // --> [{ id: 2, age: 24 }, { id: 3, age: 26 }, { id: 1, age: 27 }]
// 한편, 'undefined'는 비교 연산은 가능하지만 사실 비교가 불가능한(비교의 의미가 없는) 데이터입니다.
// 아래 예제를 참고하시기 바랍니다.
// console.log(undefined > 0); // --> false
// console.log(undefined < 0); // --> false
// console.log(undefined == 0); // --> false
// console.log(undefined === 0); // --> false
// console.log(undefined > 'hello'); // --> false
// console.log(undefined < 'hello'); // --> false
// 이러한 이유로 정렬하려는 데이터들 중 'undefined'가 있는 경우,
// 1) 'undefined' 값을 제외(filter)하고 비교하거나
// 2) 'undefined' 값을 어떤 다른 값으로 간주하여 비교해야 합니다.
// 이번 스프린트에서는 2)번의 방식을 적용하였습니다.
// 마지막 테스트 케이스의 transform 함수(byHeightAsc)를 확인하시기 바랍니다.
// 힌트
// 1. Array.prototype.sort를 사용할 수 있습니다.
// 참고 문서: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
// 예제를 통해 내장 메소드 sort의 특성을 꼭 확인하시기 바랍니다.
// 2. _.identity를 사용할 수 있습니다.
// 이번 스프린트는 정렬 자체를 다루지 않으니 스프린트 이후에 스스로 학습하시기 바랍니다.
// 학습 우선순위: bubble sort, insertion sort, quick sort, merge sort, radix sort
_.sortBy = function (arr, transform, order) {
order = order || 1;
transform = transform || _.defaults;
const arrCloned = [...arr];
return arrCloned.sort((a, b) => {
if (transform(a) < transform(b)) {
return -1 * order;
}
return order;
});
};
// _.shuffle은 배열 요소의 순서가 랜덤하게 변경된 새로운 배열을 리턴합니다.
// 다양한 상황(예. 비디오 또는 음악 재생의 순서를 섞을 때)에서 유용하게 쓰일 수 있습니다.
// _.shuffle의 동작을 이해하는 것이 목적이므로, 구현할 필요는 없습니다.
// 아래에 이미 구현된 코드를 이해하시고, 직접 테스트해 보시기 바랍니다.
// 직접 도전을 하고 싶은 경우, 아래 사이트에서 테스트 해볼 수 있습니다.
// https://bost.ocks.org/mike/shuffle/compare.html
// 단, 해당 사이트의 shuffle 함수는 입력으로 전달되는 array의 요소들의 위치를 '직접' 변경해야 합니다.
_.shuffle = function (arr) {
let arrCloned = arr.slice();
for (let fromIdx = 0; fromIdx < arr.length; fromIdx++) {
const toIdx = Math.floor(Math.random() * arr.length);
// 아래 코드는 두 변수의 값을 교환합니다.
let temp = arrCloned[fromIdx];
arrCloned[fromIdx] = arrCloned[toIdx];
arrCloned[toIdx] = temp;
}
return arrCloned;
};