前端进阶算法 --002(es6)

125 阅读7分钟

1. 数组扁平化、去重、排序

// 扁平化
const flattenDeep = (array) => array.flat(Infinity)

// 去重
const unique = (array) => Array.from(new Set(array))

// 排序
const sort = (array) => array.sort((a, b) => a-b)

// 函数组合
const compose = (...fns) => (initValue) => fns.reduceRight((y, fn) => fn(y), initValue)

// 组合后函数
const flatten_unique_sort = compose( sort, unique, flattenDeep)

// 测试
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
console.log(flatten_unique_sort(arr))
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

补充:flat() 方法对node版本有要求,至少需要12.0以上,通用方法是通过array.some() + concat来实现这个flat(),这个对node版本的限制比较低,可行性较高。 源码:

let arr =[[1, 2, 2][3, 4, 5, 5][6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]
function newArray ( ) {
  while(arr.some(Array.isArray)){
    arr = [].concat(...arr)
  }
  arr = [...new Set(arr)].sort((a,b)=>a-b)
  return arr
}
newArray()
console.log(arr);

2. JavaScript两个数组的交集

解题思路

  • filter 过滤
  • Set 去重
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersection = function(nums1, nums2) {
    return [...new Set(nums1.filter((item)=>nums2.includes(item)))]
};

3. 编写一个函数计算多个数组的交集

3.1 万能reduce

const getIntersection = (...arrs) => {
    return Array.from(new Set(arrs.reduce((total, arr) => {
        return arr.filter(item => total.includes(item));
    })));
}

3.2

  • filter 过滤
  • every 筛选
let intersection = (list , ...args) => list.filter( item => args.every( list => list.includes( item )))
console.log( intersection( [ 2,1 ], [ 2,3 ] ) ) // [ 2 ]
console.log( intersection( [ 2,1 ], [ 4,3 ] ) ) // [ ]

4. 在排序数组中查找元素的第一个和最后一个位置

4.1 解答一:findIndex、lastIndexOf

findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。

lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。

4.2 解答二:二分查找

let searchRange = function(nums, target) {
    return [leftSearch(nums, target), rightSearch(nums, target)]
}

let leftSearch = function(nums, target) {
    let low = 0, 
        high = nums.length - 1,
        mid
    while (low <= high) {
        mid = Math.floor((low+high)/2)
        if (nums[mid] < target) {
            low = mid + 1
        } else if (nums[mid] > target) {
            high = mid - 1
        } else if (nums[mid] === target) {
            // 这里不返回,继续收缩左侧边界
            high = mid - 1
        }
    }
    // 最后检查 low 是否越界或命中
    if (low >= nums.length || nums[low] != target)
        return -1
    return low
}


let rightSearch = function (nums, target) {
    let low = 0, 
        high = nums.length - 1,
        mid
    while (low <= high) {
        mid = Math.floor((low+high)/2)
        if (nums[mid] < target) {
            low = mid + 1
        } else if (nums[mid] > target) {
            high = mid - 1
        } else if (nums[mid] === target) {
            // 这里不返回,继续收缩右侧边界
            low = mid + 1
        }
    }
    // 最后检查 high 是否越界或命中
    if (high < 0 || nums[high] != target)
        return -1
    return high
}

简单二分查找

var searchRange = function(nums, target) {
  let mid;
  let low = 0;
  let high = nums.length - 1;

  while (low <= high) {
    mid = Math.floor((low + high) / 2);
    if (nums[mid] === target) {
      let start = end = mid;
      while (nums[start] === target) start--;
      while (nums[end] === target) end++;

      return [start + 1, end - 1];
    }

    if (nums[mid] > target) {
      high = mid - 1;
    } else {
      low = mid + 1;
    }
  }

  return [-1, -1];
}

5. 手写数组去重、扁平化函数

5.1 数组去重函数

方案1: 借助哈希表数据结构记录出现过的元素

  1. 遍历数组, 使用 Object 辅助记录出现过的元素
  2. 通过 Object 重建数组,也可以使用一个空数组在遍历时直接存储Object中未出现的元素.
function deduplicate(arr) {
  var obj = Object.create(null)
  var res = []

  for (var i = 0; i < arr.length; i++) {
    var element = arr[i]
    if (!(element in obj)) {
      res.push(element)
    }
  }
  return res
}

5.2 扁平化函数

5.21 使用 reduce 方法:

一次性扁平化所有:

function flattenDeep(arr) { 
    return Array.isArray(arr)
      ? arr.reduce( (acc, cur) => [...acc, ...flattenDeep(cur)] , [])
      : [arr]
}

// 测试
var test = ["a", ["b", "c"], ["d", ["e", ["f"]], "g"]]
flattenDeep(test)
// ["a", "b", "c", "d", "e", "f", "g"]

5.22 扁平化函数

适用条件: 数组或类数组对象, 对于嵌套数组可通过depth指定扁平化的深度.

/**
 * @param {array}arr The array to be flatten.
 * @param {number}depth The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
 */
function flat(arr, depth = 1) {

  var res = [] 
  var unflattenDepth = depth

  var isArray = Object.prototype.toString.call(arr) === '[object Array]'
  if (isArray) {
    helper(arr)
  } else { // 将类数组对象处理为数组
    var slicedArr = Array.prototype.slice.call(arr)
    if (slicedArr.length > 0) {
      helper(slicedArr)
    }
  }

  return res

  function helper(arr) {
    unflattenDepth -= 1

    if (unflattenDepth < 0) {
      res = res.concat(arr)
    } else {
      for (var i = 0; i < arr.length; i++) {
        var element = arr[i]

        if (Object.prototype.toString.call(element) === '[object Array]') {
          helper(element)
        } else {
          res.push(element)
        }
      }
    }
  }
}

6. 不产生新数组,删除数组里的重复元素

6.1 方式一:排序去重

MDN:sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的

const removeDuplicates = (nums) => {
    // 原地排序
    nums.sort()
    // 去重
    let len = 1
    for (let i = 1; i < nums.length; i++)
        if (nums[i] != nums[i-1]) nums[len++] = nums[i];
    // 删除重复项
    nums.splice(len)
    return nums
}

// 测试
removeDuplicates([1, 2, 3, 1, 3])
// [1, 2, 3]

6.2 方式二:优化

const removeDuplicates = (nums) => {
    let len = nums.length - 1
    for(let i = len; i>=0; i--) {
        if(nums.indexOf(nums[i]) != i) {
            nums[i] = nums[len --]
        }
    }
    // 删除重复项
    nums.splice(len+1)
    return nums
}
// 测试
removeDuplicates([1, 2, 3, 1, 3])
// [1, 2, 3]

7. 写一个数组(包含对象等类型元素)去重函数

  • 采用 reduce 去重,初始 accumulator 为 []
  • 采用 findIndex 找到 accumulator 是否包含相同元素,如果不包含则加入,否则不加入
  • 返回最终的 accumulator ,则为去重后的数组
// 获取类型
const getType = (function() {
    const class2type = {
        '[object Boolean]': 'boolean', 
        '[object Number]': 'number', 
        '[object String]': 'string', 
        '[object Function]': 'function', 
        '[object Array]': 'array', 
        '[object Date]': 'date', 
        '[object RegExp]': 'regexp', 
        '[object Object]': 'object', 
        '[object Error]': 'error', 
        '[object Symbol]': 'symbol' 
    }

    return function getType(obj) {
        if (obj == null) {
            return obj + ''
        }
        // javascript高级程序设计中提供了一种方法,可以通用的来判断原始数据类型和引用数据类型
        const str = Object.prototype.toString.call(obj)
        return typeof obj === 'object' || typeof obj === 'function' ? class2type[str] || 'object' : typeof obj
    };
})();

/**
 * 判断两个元素是否相等
 * @param {any} o1 比较元素
 * @param {any} o2 其他元素
 * @returns {Boolean} 是否相等
 */
const isEqual = (o1, o2) => {
    const t1 = getType(o1)
    const t2 = getType(o2)

    // 比较类型是否一致
    if (t1 !== t2) return false
    
    // 类型一致
    if (t1 === 'array') {
        // 首先判断数组包含元素个数是否相等
        if (o1.length !== o2.length) return false 
        // 比较两个数组中的每个元素
        return o1.every((item, i) => {
            // return item === target
            return isEqual(item, o2[i])
        })
    }

    if (t2 === 'object') {
        // object类型比较类似数组
        const keysArr = Object.keys(o1)
        if (keysArr.length !== Object.keys(o2).length) return false
        // 比较每一个元素
        return keysArr.every(k => {
            return isEqual(o1[k], o2[k])
        })
    }

    return o1 === o2
}

// 数组去重
const removeDuplicates = (arr) => {
    return arr.reduce((accumulator, current) => {
        const hasIndex = accumulator.findIndex(item => isEqual(current, item))
        if (hasIndex === -1) {
            accumulator.push(current)
        }
        return accumulator
    }, [])
}

// 测试
removeDuplicates([123, {a: 1}, {a: {b: 1}}, {a: "1"}, {a: {b: 1}}, "meili", {a:1, b:2}, {b:2, a:1}])
// [123, {a: 1}, a: {b: 1}, {a: "1"}, "meili", {a: 1, b: 2}]

8. 实现一个 findIndex 函数

function findIndex(list, target){
    let start = 0, end = list.length -1;
    let index = -1;
    while(start !=end && start < end){
        let mid = start + Math.floor((end - start ) / 2);
        if(list[mid] > target){
            end = mid -1;
        }else if (list[mid] < target){
            start = mid + 1;
        }else{
            index = mid;
            end = mid -1;
        }
    }
    return index;
 }

仿照python的bisect-left的源码:

function findIndex(arr: number[], target: number) {
    let lo = 0;
    let hi = arr.length - 1;
    while (lo < hi) {
        let mid = Math.floor((lo + hi) / 2);
        if (target > arr[mid]) {
            lo = mid + 1;
        } else {
            hi = mid;
        }
    }
    return lo;
}

9. 模拟实现 Array.prototype.splice

Array.prototype.splice() 的用法如下:

  • array.splice(start) :删除数组中从下标 start 开始(包含 start )的所有元素
  • array.splice(start, deleteCount) :删除数组中从下标 start 开始(包含 start )的 deleteCount 元素
  • array.splice(start, deleteCount, item1, item2, ...) :删除数组中从下标 start 开始(包含 start )的 deleteCount 元素,然后在相同位置上插入 item1, item2, ...

特征包括如下:

  • start :可正可负,正数表示从下标为 start 的位置开始修改,如果 start > array.length - 1  ,则表示从数组末尾处开始修改;负数表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n
  • deleteCount :表示从 start 开始要移除的元素个数,省略则表示把 start 之后的所有元素都移除,如果是 0 或负数,则不移除元素
  • item1, item2, ... :要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素
  • 返回:被删除的元素组成的一个数组

实现思路:

  • 处理 start 负数或超出边界问题,计算真实有效的开始位置 startIndex

  • 处理 deleteCount 负数问题,计算真实有效的删除元素个数 delCount

  • 从 startIndex 开始删除 delCount 个元素并原地添加 item1, item2, … (添加元素个数为 addCount )

    • 拷贝删除的 delCount 到新数组 deletedElements ,用于 array.splice 函数返回
    • 如果 delCount > addCount (删除的元素个数大于添加元素):将数组中 startIndex + delCount 后的元素向前移动 delCount - addCount 个位置,将添加元素拷贝进来
    • 如果 delCount = addCount (删除的元素个数等于添加元素):直接将添加元素覆盖删除元素即可
    • 如果 delCount < addCount (删除的元素个数小于添加元素):将数组中 startIndex + delCount 后的元素向后移动 addCount - delCount 个位置,将元素拷贝进来
  • 返回 deletedElements

完整代码实现:

Array.prototype._splice = function(start, deleteCount) {
    // 入参元素个数
    let argumentsLen = arguments.length
    // 数组
    let array = Object(this)
    // 数组长度
    let len = array.length
    // 添加元素个数
    let addCount = argumentsLen > 2 ? argumentsLen -2 : 0
    // 计算有效的 start
    let startIndex = computeSpliceStartIndex(start, array)
    // 计算有效的 deleteCount
    let delCount = computeSpliceDeleteCount(startIndex, deleteCount, len)
    // 记录删除的数组元素
    let deletedElements = new Array(delCount)
    
    // 将待删除元素记录到 deletedArray
    recordDeleteElements(startIndex, delCount, array, deletedElements)
    
    // 密封对象
    if(delCount !== addCount && Object.isSealed(array)) {
        throw new TypeError('the array is sealed')
    }
    // 冻结对象
    if(delCount > 0 && addCount > 0 && Object.isFrozen(array)) {
        throw new TypeError('the array is frozen')
    }
    
    // 移动数组元素
    moveElements(startIndex, delCount, array, addCount)
    
    let i = startIndex
    let argumentsIndex = 2
    
    // 插入新元素
    while (argumentsIndex < argumentsLen) {
        array[i++] = arguments[argumentsIndex++]
    }
    
    array.length = len - delCount + addCount

    // 返回删除元素数组
    return deletedElements;
}

// 计算真实的 start
function computeSpliceStartIndex(start, len) {
    // 处理负值,如果负数的绝对值大于数组的长度,则表示开始位置为第0位
    if(start < 0) {
        start += len
        return start < 0 ? 0 : start
    }
    // 处理超出边界问题
    return start > len - 1 ? len - 1: start
} 

// 计算真实的 deleteCount
function computeSpliceDeleteCount(startIndex, deleteCount, len) {
    // 超出边界问题
    if(deleteCount > len - startIndex) deleteCount = len - startIndex
    // 负值问题
    if(deleteCount < 0) deleteCount = 0
    return deleteCount
}

// 记录删除元素,用于 Array.prototype.splice() 返回
function recordDeleteElements(startIndex, delCount, array, deletedElementd) {
    for(let i = 0; i < delCount; i++) {
        deletedElementd[i] = array[startIndex + i]
    }
}

// 移动数组元素,便于插入新元素
function moveElements(startIndex, delCount, array, addCount) {
    let over = addCount - delCount
    if(over) {
        // 向后移
        for(let i = array.length - 1; i >= startIndex + delCount; i--) {
            array[i+over] = array[i]
        }
    } else if (over < 0) {
        // 向前移
        for(let i = startIndex + delCount; i <= array.length - 1; i++) {
            if(i + Math.abs(over) > array.length - 1) {
                // 删除冗于元素
                delete array[i]
                continue
            }
            array[i] = array[i + Math.abs(over)]
        }
    }
}

const months = ['Jan', 'March', 'April', 'June']
console.log(months._splice(1, 0, 'Feb'))
// []
console.log(months)
// ["Jan", "Feb", "March", "April", "June"]

console.log(months._splice(4, 1, 'May'))
// ["June"]
console.log(months)
// ["Jan", "Feb", "March", "April", "May"]

Array.prototype.splice源码地址

10. 实现一个add方法

10.1 闭包和this实现:

function add() {
  const args = Array.prototype.slice.apply(arguments);
  const self = this;
  this.nums = [...args];
  function _add() {
    const _args = Array.prototype.slice.apply(arguments);
    this.nums.push(..._args);
    return _add; 
  }
  _add.value = function() {
    return self.nums.reduce((acc, cur) => acc += cur, 0);
  }
  return _add;
}

10.2 reduce实现

const add = (...args) => {
    const _add = (...args1) => {
        return add(...args, ...args1)
    }
    _add.value = () => args.reduce((t, e) => t+e)

    return _add
}
add(1)(2,3)(4).value()