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: 借助哈希表数据结构记录出现过的元素
- 遍历数组, 使用
Object辅助记录出现过的元素 - 通过
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"]
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()