제로하우스

[JavaScript] Underbar 라이브러리 구현하기 본문

기타/코드스테이츠 블록체인 부트캠프

[JavaScript] Underbar 라이브러리 구현하기

송제로투 2022. 5. 19. 18:18

배열, 객체를 다루는 Underbar 라이브러리를 구현해본다.
Underbar의 모티브가 되는 라이브러리는 Underscore.js, Lodash 등이 있다.

 

_.identity

_.identity는 전달인자가 무엇이든 그대로 리턴한다. 이 함수는 underbar의 기능 구현 및 테스트를 위해 재사용되는 함수이다.

_.identity = function (val) {
  return val
};

 

_.slice

_.slice는 배열의 start 인덱스부터 end 인덱스 이전까지의 요소를 shallow copy하여 새로운 배열을 리턴한다.

_.slice = function (arr, start, end) {
  let _start = start || 0,
    _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

_.take는 배열의 처음 n개의 요소를 담은 새로운 배열을 리턴한다. n이 undefined이거나 음수인 경우 빈 배열을 리턴하며, n이 배열의 길이를 초과할 경우에는 전체 배열을 shallow copy한 새로운 배열을 리턴한다.

_.take = function (arr, n) {
  if (n === undefined || n < 0) {
    return []
  }
  else if (n >= arr.length) {
    let result = arr
    return result
  }
  else {
    let result = []
    for (let i = 0; i < n; i++) {
      result.push(arr[i])
    }
    return result
  }
};

 

_.drop

_.drop_.take와 반대로 처음 n개의 요소를 제외한 새로운 배열을 리턴한다.n이 undefined이거나 음수인 경우 전체 배열을 shallow copy한 새로운 배열을 리턴하며, n이 배열의 길이를 초과할 경우에는 빈 배열을 리턴한다.

_.drop = function (arr, n) {
  if (n === undefined || n < 0) {
    let result = arr
    return result
  }
  else if (n >= arr.length) {
    return []
  }
  else {
    let result = []
    for (let i = n; i < arr.length; i++) {
      result.push(arr[i])
    }
    return result
  }
};

 

_.last

_.last는 배열의 마지막 n개의 요소를 담은 새로운 배열을 리턴한다. n이 undefined이거나 음수인 경우 배열의 마지막 요소만을 담은 배열을 리턴하며, n이 배열이 길이를 초과할 경우 전체 배열을 shallow_copy한 새로운 배열을 리턴한다.

_.last = function (arr, n) {
  if (n === undefined || n < 0) {
    let result = [arr[arr.length-1]]
    return result
  }
  else if (n >= arr.length) {
    let result = arr
    return result
  }
  else {
    let result = []
    for (let i = arr.length - n; i < arr.length; i++) {
      result.push(arr[i])
    }
    return result
  }

};

 

_.each

_.each는 collection의 각 데이터에 반복적인 작업을 수행한다. _.each는 명시적으로 어떤 값을 리턴하지 않는다.

_.each = function (collection, iteratee) {
  // TODO: 여기에 코드를 작성합니다.
  // console.log(collection)
  // console.log(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

_.indexOf는 target으로 전달되는 값이 arr의 요소인 경우, 배열에서의 index를 리턴한다. 존재하지 않는 경우 -1을 리턴하며, target이 중복해서 존재하는 경우에는 가장 낮은 index를 리턴한다.

_.indexOf = function (arr, target) {
  let result = -1;

  _.each(arr, function (item, index) {
    if (item === target && result === -1) {
      result = index;
    }
  });

  return result;
};

 

_.filter

_.filter는 test 함수를 통과하는 모든 요소를 담은 새로운 배열을 리턴한다.

_.filter = function (arr, test) {
  let result = []
  _.each(arr, function(item, index) {
    if (test(item)) {
      result.push(item)
    }
  })
  return result
};

 

_.reject

_.reject_.filter와 정반대로 test 함수를 통과하지 않는 모든 요소를 담은 새로운 배열을 리턴한다.

_.reject = function (arr, test) {
  let result = []
  _.each(arr, function(item, index) {
    if (!test(item)) {
      result.push(item)
    }
  })
  return result
};

 

_.uniq

_.uniq는 주어진 배열의 요소가 중복되지 않도록 새로운 배열을 리턴한다.

_.uniq = function (arr) {
  let result = []
  _.each(arr, function(item) {
    if(_.indexOf(result, item) === -1) {
      result.push(item)
    }
  })
  return result
};

 

_.map

_.map은 iteratee(반복 작업)를 arr(배열)의 각 요소에 적용한 결과를 담은 새로운 배열을 리턴한다.

_.map = function (arr, iteratee) {
  let result = []
  _.each(arr, function(item, index) {
    result.push(iteratee(item))
  })
  return result
};

 

_.pluck

_.pluck는 객체 또는 배열을 요소로 갖는 배열과 각 요소에서 찾고자 하는 key 또는 index를 입력받아 각 요소의 해당 값 또는 요소만을 추출하여 새로운 배열에 저장하고, 최종적으로 새로운 배열을 리턴한다.

_.pluck = function (arr, keyOrIdx) {
  let result = []
  _.map(arr, function(item) {
    result.push(item[keyOrIdx])
  })
  return result
};

 

_.reduce

_.reduce는 배열을 순회하며 각 요소에 iteratee 함수를 적용하고, 그 결과값을 계속해서 누적한 후, 최종적으로 누적된 결과값을 리턴한다.

_.reduce = function (arr, iteratee, initVal) {
  let acc = initVal

  _.each(arr, function(item, index, arr) {
    if (initVal === undefined && index === 0) {
      acc = item
    }
    else {
      acc = iteratee(acc, item, index, arr)
    }
  })

  return acc
};

 

_.once

_.once는 callback 함수를 한 번만 호출하는 '함수'를 리턴한다. _.once가 리턴하는 함수를 여러 번 호출해도 callback 함수는 한 번 이상 호출되지 않는다.

_.once = function (func) {
  let isCalled = false;
  let result
  return function () {
    if (!isCalled) {
      result = func(...arguments)
      isCalled = true
    }
    return result
  };
};

 

_.delay

_.delay는 입력으로 전달되는 시간(ms, 밀리초) 후, callback 함수를 함께 전달되는 인자와 함께 실행한다.

_.delay = function (func, wait, ...args) {
  setTimeout(func, wait, ...args)
};

 

_.includes

`_.includes`는 배열이 주어진 값을 포함하는지 확인한다.

_.includes = function (arr, target) {
  let flag = false
  _.each(arr, function (item, index) {
    if (item === target) {
      flag = true
    }
  });
  return flag
};

 

_.every

_.every는 배열의 모든 요소가 test 함수(iteratee)를 통과하면 true, 그렇지 않은 경우 false를 리턴한다.

_.every = function (arr, iteratee) {
  let flag = true
  for (let el of arr) {
    if (iteratee == undefined && !el) {
      flag = false
    }
    else if (iteratee != undefined && !iteratee(el)) {
      flag = false
    }
  }
  return flag
};

 

_.some

_.some은 배열의 요소 중 하나라도 test 함수(iteratee)를 통과하면 true를, 그렇지 않은 경우 false를 리턴한다.

_.some = function (arr, iteratee) {
  // TODO: 여기에 코드를 작성합니다.
  if (arr.length < 1) {
    return false
  }
  let flag = false
  for (let el of arr) {
    if (iteratee == undefined && el) {
      flag = true
    }
    else if (iteratee != undefined && iteratee(el)) {
      flag = true
    }
  }
  return flag
};

 

_.extend

_.extend는 여러 개의 객체를 입력받아, 순서대로 객체를 결합한다.

_.extend = function (obj) {
  _.each(arguments, function(arg) {
    for(var key in arg){
      obj[key] = arg[key];
    }
  })
  return obj
};

 

_.defaults

_.defaults는 `_.extend와 비슷하게 동작하지만, 이미 존재하는 속성(key)을 덮어쓰지 않는다.

_.defaults = function (obj) {
  _.each(arguments, function(arg) {
    for(var key in arg){
      if (!obj.hasOwnProperty(key)) {
        obj[key] = arg[key];
      }
    }
  })
  return obj
};

 

_.zip

_.zip은 여러 개의 배열을 입력받아, 같은 index의 요소들을 묶어 배열로 만든다.

_.zip = function () {
  var len = arguments[0].length
  for (let arg of arguments) {
    if (arg.length > len) {
      len = arg.length
    }
  }
  let result = []
  for (let i = 0; i < len; i++) {
    result.push([])
  }
  _.each(arguments, function(arg) {
    for (let i = 0; i < len; i++) {
      if (typeof result[i] == 'undefined') {
        result[i].push(undefined)
      }
      else {
        result[i].push(arg[i])
      }
    }
  })
  return result
};

 

_.zipStrict

_.zipStrict_.zip과 비슷하게 동작하지만, 최종적으로 리턴되는 배열의 각 요소의 길이는 입력으로 전달되는 배열 중 가장 "짧은" 배열의 길이로 통일된다.

_.zipStrict = function () {
  var len = arguments[0].length
  for (let arg of arguments) {
    if (arg.length < len) {
      len = arg.length
    }
  }
  let result = []
  for (let i = 0; i < len; i++) {
    result.push([])
  }
  _.each(arguments, function(arg) {
    for (let i = 0; i < len; i++) {
        result[i].push(arg[i])
    }
  })
  return result
};

 

_.intersection

_.intersection은 여러 개의 배열을 입력받아 교집합 배열을 리턴한다.

_.intersection = function () {
  let result = []
  let set = arguments[0]
  // 기준 요소를 비교
  for (let el of set) {
    let flag = true
    _.each(arguments, function(arg) {
      if (!_.includes(arg, el)) {
        flag = false
      }
    })
    if (flag) {
      result.push(el)
    }
  }
  return result
};

 

_.difference

_.difference는 여러 개의 배열을 입력받아 차집합 배열을 리턴한다.

_.difference = function () {
  let result = []
  let keys = []
  let times = []  // key가 등장한 time
  for (let i = 0; i < arguments[0].length; i++) {
    keys.push(arguments[0][i])
    times.push(0)
  }
  // 기준 요소를 비교
  for(let i = 1; i <arguments.length; i++) {
    _.each(arguments[i], function(el) {
      if (_.includes(keys, el)) {
        let index = _.indexOf(keys, el)
        times[index]++
      }
    })
  }
  for (let i = 0; i <times.length; i++) {
    if (times[i] === 0) {
      result.push(keys[i])
    }
  }
  return result
};

 

_.shuffle

_.shuffle은 배열 요소의 순서가 랜덤하게 변경된 새로운 배열을 리턴한다.

_.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;
};

 

Comments