[JavaScript] Underbar bareMinimums 과제
2022.11.22
💡 오늘의 JavaScript 공부
▶️ bareMinimums
함수를 직접 구현하라
📌 _.slice. [ 예시 ]
_.slice = function (arr, start, end) {
// 변수를 선언할 경우, 아래와 같이 콤마(,)를 이용해 선언할 수 있습니다.
// 이때, 콤마로 연결된 변수들은 모두 동일한 선언 키워드(let, const)가 적용됩니다.
// 이런 코딩 스타일도 가능하다는 것을 보여드리기 위한 예시일 뿐, 사용을 권장하는 것은 아닙니다.
// 오픈 소스에 기여하든, 회사 내에서 개발을 하든 본인이 속한 조직의 코딩 스타일, 코딩 컨벤션을 따르면 됩니다.
// 그리고 아래와 같은 코딩 스타일을 봐도 당황하지 않고 해석할 수 있으면 됩니다.
let _start = start || 0, // `start`가 undefined인 경우, slice는 0부터 동작합니다.
_end = end;
// 입력받은 인덱스가 음수일 경우, 마지막 인덱스부터 매칭한다. (예. -1 => arr.length - 1, -2 => arr.length - 2)
// 입력받은 인덱스는 0 이상이어야 한다.
if (start < 0) _start = Math.max(0, arr.length + start);
if (end < 0) _end = Math.max(0, arr.length + end);
// `end`가 생략될 경우(undefined), slice는 마지막 인덱스까지 동작합니다.
// `end`가 배열의 범위를 벗어날 경우, slice는 마지막 인덱스까지 동작합니다.
if (_end === undefined || _end > arr.length) _end = arr.length;
let result = [];
// `start`가 배열의 범위를 벗어날 경우, 빈 배열을 리턴합니다.
for (let i = _start; i < _end; i++) {
result.push(arr[i]);
}
return result;
};
📌 ._take
- _.take는 배열의 처음 n개의 element를 담은 새로운 배열을 리턴합니다.
- n이 undefined이거나 음수인 경우, 빈 배열을 리턴합니다.
- n이 배열의 길이를 벗어날 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴합니다.
_.take = function (arr, n) {
// TODO: 여기에 코드를 작성합니다.
let newArr = [];
if(n === undefined || n < 0){
return []
}else if (n > arr.length){
return _.slice(arr,0,n)
}
for(let i = 0; i < n; i++){
newArr.push(arr[i])
}
return newArr
};
📌 ._drop
- _.drop는 _.take와는 반대로, 처음 n개의 element를 제외한 새로운 배열을 리턴합니다.
- n이 undefined이거나 음수인 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴합니다.
- n이 배열의 길이를 벗어날 경우, 빈 배열을 리턴합니다.
_.drop = function (arr, n) {
// TODO: 여기에 코드를 작성합니다.
let newArr = [];
if(n === undefined || n < 0){
return _.slice(arr,0,n)
}else if(n > arr.length){
return []
}
for(let i = n; i < arr.length; i++){
newArr.push(arr[i])
}
return newArr
};
📌 ._last
- _.last는 배열의 마지막 n개의 element를 담은 새로운 배열을 리턴합니다.
- n이 undefined이거나 음수인 경우, 배열의 마지막 요소만을 담은 배열을 리턴합니다.
- n이 배열의 길이를 벗어날 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴합니다.
- _.take와 _.drop 중 일부 또는 전부를 활용할 수 있습니다.
_.last = function (arr, n) {
// TODO: 여기에 코드를 작성합니다.
let newArr = [];
if(n > arr.length){
return _.slice(arr,0,n)
}else if(n === undefined || n < 0){
return newArr = [arr[arr.length-1]]
}else if(n === 0){
return newArr
}
for(let i = n - 1; i < arr.length; i++){
newArr.push(arr[i])
}
return newArr
};
📌 ._each
- collection(배열 혹은 객체)과 함수 iteratee(반복되는 작업)를 인자로 전달받아
- collection의 데이터(element 또는 property)를 순회하면서
- iteratee에 각 데이터를 인자로 전달하여 실행합니다.
_.each는 collection의 각 데이터에 반복적인 작업을 수행합니다.
// testcase
const letters = ['a', 'b', 'c'];
const iterations = [];
_.each(letters, function(letter) {
iterations.push(letter);
});
expect(iterations).to.eql(['a', 'b', 'c']);
// iteratee는 차례대로 데이터(element 또는 value), 접근자(index 또는 key), collection을 다룰 수 있어야 합니다.
// 배열 arr을 입력받을 경우, iteratee(ele, idx, arr)
// 객체 obj를 입력받을 경우, iteratee(val, key, obj)
// 이처럼 collection의 모든 정보가 iteratee의 인자로 잘 전달되어야 모든 경우를 다룰 수 있습니다.
// 실제로 전달되는 callback 함수는 collection의 모든 정보가 필요하지 않을 수도 있습니다.
// myCode
_.each = function (collection, iteratee) {
// TODO: 여기에 코드를 작성합니다.
//배열 arr을 입력받을 경우, iteratee(ele, idx, arr) 조건문 써서 collection이 arr이면 반복문으로 collection 데이터 순회
//객체 obj를 입력받을 경우, iteratee(val, key, obj)
if(Array.isArray(collection)){
for(let i = 0; i < collection.length; i++){
iteratee(collection[i], i, collection)
}
}else if(typeof collection === 'object'){
for(let key in collection){
iteratee(collection[key],key,collection)
}
}
};
📌 ._indexOf
- _.indexOf는 target으로 전달되는 값이 arr의 요소인 경우, 배열에서의 위치(index)를 리턴합니다.
- 그렇지 않은 경우, -1을 리턴합니다.
- target이 중복해서 존재하는 경우, 가장 낮은 index를 리턴합니다.
_.indexOf = function (arr, target) {
// 배열의 모든 요소에 접근하려면, 순회 알고리즘(iteration algorithm)을 구현해야 합니다.
// 반복문을 사용하는 것이 가장 일반적이지만, 지금부터는 이미 구현한 _.each 함수를 활용하여야 합니다.
// 아래 _.indexOf의 구현을 참고하시기 바랍니다.
let result = -1;
_.each(arr, function (item, index) {
if (item === target && result === -1) {
result = index;
}
});
return result;
};
📌 ._filter
- _.filter는 test 함수를 통과하는 모든 요소를 담은 새로운 배열을 리턴합니다.
- test(element)의 결과(return 값)가 truthy일 경우, 통과입니다.
- test 함수는 각 요소에 반복 적용됩니다.
_.filter = function (arr, test) {
// TODO: 여기에 코드를 작성합니다.
let newArr = [];
_.each(arr, function(element,index,arr){
if(test(element)){
newArr.push(element)
}
})
return newArr
};
📌 ._reject
- _.reject는 _.filter와 정반대로 test 함수를 통과하지 않는 모든 요소를 담은 새로운 배열을 리턴합니다.
_.reject = function (arr, test) {
// TODO: 여기에 코드를 작성합니다.
// _.each함수를 사용해야 합니다.
let newArr = [];
_.each(arr, function(element,index,arr){
if(!test(element)){
newArr.push(element)
}
})
return newArr
};
📌 ._uniq
- _.uniq는 주어진 배열의 요소가 중복되지 않도록 새로운 배열을 리턴합니다.
- 중복 여부의 판단은 엄격한 동치 연산(strict equality, ===)을 사용해야 합니다.
- 입력으로 전달되는 배열의 요소는 모두 primitive value라고 가정합니다.
_.uniq = function (arr) {
// TODO: 여기에 코드를 작성합니다.
// _.each 함수를 사용해야 합니다.
// _. 중복된 숫자는 하나만 => _.indexOf사용해야겠다
let newArr = [];
_.each(arr,function(i){
if(_.indexOf(newArr,i) === -1){
newArr.push(i)
}
})
return newArr
};
📌 ._map
- _.map은 iteratee(반복되는 작업)를 배열의 각 요소에 적용(apply)한 결과를 담은 새로운 배열을 리턴합니다.
- 함수의 이름에서 드러나듯이 _.map은 배열의 각 요소를 다른 것(iteratee의 결과)으로 매핑(mapping)합니다.
_.map = function (arr, iteratee) {
// TODO: 여기에 코드를 작성합니다.
// _.map 함수는 매우 자주 사용됩니다.
// _.each 함수와 비슷하게 동작하지만, 각 요소에 iteratee를 적용한 결과를 리턴합니다.
let newArr = [];
_.each(arr,function(elemnet){
newArr.push(iteratee(elemnet))
})
return newArr
};
📌 ._pluck
- 객체 또는 배열을 요소로 갖는 배열과 각 요소에서 찾고자 하는 key 또는 index를 입력받아
- 각 요소의 해당 값 또는 요소만을 추출하여 새로운 배열에 저장하고,
- 최종적으로 새로운 배열을 리턴합니다.
예를 들어, 각 개인의 정보를 담은 객체를 요소로 갖는 배열을 통해서, 모든 개인의 나이만으로 구성된 별도의 배열을 만들 수 있습니다.
최종적으로 리턴되는 새로운 배열의 길이는 입력으로 전달되는 배열의 길이와 같아야 합니다.
따라서 찾고자 하는 key 또는 index를 가지고 있지 않은 요소의 경우, 추출 결과는 undefined 입니다.
//_.pluck을 _.each를 사용해 구현하면 아래와 같습니다.
let result = [];
_.each(arr, function (item) {
result.push(item[keyOrIdx]);
});
return result;
//_.pluck은 _.map을 사용해 구현하시기 바랍니다.
// my code
_.pluck = function (arr, keyOrIdx) {
let newArr = [];
_.map(arr,function(element){
newArr.push(element[keyOrIdx])
})
return newArr
};
📌 _.reduce
- 배열을 순회하며 각 요소에 iteratee 함수를 적용하고,
- 그 결과값을 계속해서 누적(accumulate)합니다.
- 최종적으로 누적된 결과값을 리턴합니다.
_.reduce는 위에서 구현한 많은 함수처럼, 입력으로 배열과 각 요소에 반복할 작업(iteratee)을 전달받습니다.
iteratee에 대해서 복습하면 아래와 같습니다. (일반적으로 객체를 reduce 하지는 않으므로, 배열 부분만 복습합니다.)
iteratee는 차례대로 데이터(element 또는 value), 접근자(index 또는 key), collection을 다룰 수 있어야 합니다.
배열 arr을 입력받을 경우, iteratee(ele, idx, arr)
_.reduce는 반복해서 값을 누적하므로 이 누적되는 값을 관리해야 합니다.
따라서 _.reduce의 iteratee는 인자가 하나 더 추가되어 최종 형태는 아래와 같습니다.
iteratee(acc, ele, idx, arr)
누적되는 값은 보통 tally, accumulator(앞글자만 따서 acc로 표기하기도 함)로 표현하거나
목적을 더 분명하게 하기 위해 sum(합), prod(곱), total 등으로 표현하기도 합니다.
이때, acc는 '이전 요소까지'의 반복 작업의 결과로 누적된 값입니다.
ele는 잘 아시다시피 반복 작업을 수행할(아직 수행하지 않은) 현재의 요소입니다.
여기까지 내용을 정리하면 다음과 같습니다.
_.reduce(arr, iteratee)
iteratee(acc, ele, idx, arr)
그런데 사실 누적값에 대해서 빠뜨린 게 하나 있습니다.
바로 '누적값은 어디서부터 시작하는가'라는 의문에 대한 대답을 하지 않았습니다.
이를 해결하는 방법은 초기 값을 직접 설정하거나 자동으로 설정하는 것입니다.
_.reduce는 세 번째 인자로 초기 값을 전달받을 수 있습니다.
이 세 번째 인자로 초기 값이 전달되는 경우, 그 값을 누적값의 기초(acc)로 하여 배열의 '첫 번째' 요소부터 반복 작업이 수행됩니다.
반면 초기 값이 전달되지 않은 경우, 배열의 첫 번째 요소를 누적값의 출발로 하여 배열의 '두 번째' 요소부터 반복 작업이 수행됩니다.
따라서 최종적인 형태는 아래와 같습니다.
_.reduce(arr, iteratee, initVal)
** initVal => 초기값 **
iteratee(acc, ele, idx, arr)
// ex code
const numbers = [1,2,3];
const sum = _.reduce(numbers, function(total, number){
return total + number;
}); // 초기 값이 주어지지 않았으므로, 초기 값은 배열의 첫 요소인 1입니다. 두 번째 요소부터 반복 작업이 시작됩니다.
// 1 + 2 = 3; (첫 작업의 결과가 누적되어 다음 작업으로 전달됩니다.)
// 3 + 3 = 6; (마지막 작업이므로 최종적으로 6이 리턴됩니다.)
const identity = _.reduce([3, 5], function(total, number){
return total + number * number;
}, 2); // 초기 값이 2로 주어졌습니다. 첫 번째 요소부터 반복 작업이 시작됩니다.
// 2 + 3 * 3 = 11; (첫 작업의 결과가 누적되어 다음 작업으로 전달됩니다.)
// 11 + 5 * 5 = 36; (마지막 작업이므로 최종적으로 36이 리턴됩니다.)
// my Code
_.reduce = function (arr, iteratee, initVal) {
// TODO: 여기에 코드를 작성합니다.
// ** _.reduce에서 iteratee는 (누적값, 데이터, 접근자, 콜렉션) **
let result = initVal;
_.each(arr,function(el,index,acc){
if(initVal === undefined && index === 0){
result = el
}else{
result = iteratee(result,el,index,acc)
}
})
return result
};
Comments