/**
* 数组工具类
*
* @class ArrayTool
*/
class ArrayTool {
constructor() {}
/**
* @description 打印开始点
*
* @memberof ArrayTool
*/
start() {
console.log("数组工具类ArrayTool start^_^_^_^_^_^_^_^_^_^")
}
/**
* @description 打印结束点
*
* @memberof ArrayTool
*/
end() {
console.log("数组工具类ArrayTool end^_^_^_^_^_^_^_^_^_^")
}
/**
* @description 查询数组中是否存在某个元素并返回元素第一次出现的下标
* @param {*} item 要查询的元素
* @param {Array} data
* @return {Number} 元素第一次出现的下标
* @memberof ArrayTool
* @example
* inArray(2,[1,2,3,4]) // 1
*/
inArray(item, data) {
for (let i = 0; i < data.length; i++) {
if (item === data[i]) {
return i
}
}
return -1
}
/**
* @description 数组乱序
* @param {Array} arr
* @return {Array}
* @memberof ArrayTool
* @example
* arrScrambling([1,5,9]) // [5,1,9]
*/
arrScrambling(arr) {
let array = arr
let index = array.length
while (index) {
index -= 1
let randomIndex = Math.floor(Math.random() * index)
let middleware = array[index]
array[index] = array[randomIndex]
array[randomIndex] = middleware
// console.log(array[index])
// console.log(array[randomIndex])
// console.log("----------")
}
return array
}
/**
* @description 数组交集(方法一)
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
* @memberof ArrayTool
* @example
* similarity([1,2,3],[5,2]) // [2]
*/
similarity(arr1, arr2) {
return arr1.filter((v) => arr2.includes(v))
}
/**
* @description has() 方法返回一个布尔值来指示对应的值value是否存在Set对象中。
* @description 数组交集(方法二)
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
* @memberof ArrayTool
* @example
* intersection([1,2,3],[5,2]) // [2]
*/
intersection(arr1, arr2) {
const s = new Set(arr2)
return arr1.filter((a1) => s.has(a1))
}
/**
* @description 对两个数组的每个元素执行了函数之后,返回两个数组中存在的元素列表。
* @description 返回arr1和arr2满足fn函数后的交集(arr1)
* @description 两数组都符合条件的交集
*
* @param {Array} arr1
* @param {Array} arr2
* @param {Function} fn
* @return {Array}
* @memberof ArrayTool
* @example
* intersectionBy([2.1,2.5, 1.2], [2.3, 3.4], Math.floor) // [2.1, 2.5]
*/
intersectionBy(arr1, arr2, fn) {
const s = new Set(arr2.map(fn))
return arr1.filter((a1) => s.has(fn(a1)))
}
/**
* @description 数组中某元素出现的次数 ([1,2,2,3],2)
* @param {Array} arr
* @param {Number/String} value
* @return {Number}
* @memberof ArrayTool
* @example
* countOccurrences([1,2,2,3],2) // 2
* countOccurrences([1, 1, 2, 1, '1', '1', 2, 3], "1"); // 2
*/
countOccurrences(arr, value) {
// 最后面那个0是我们将index索引从0开始,也就是a默认为0
// reduce第一个参数的计算后的返回值,这里来说,a为0,因为后面加上参数0
// reduce第二个参数是数组的第n个
return arr.reduce((a, v) => (v === value ? a + 1 : a + 0), 0)
}
/**
* @description 分割指定长度的元素数组
*
* @param {Array} list 传进来的数组
* @param {number} [size=1] 要分成几个为一组的数据
* @param {Array} [cacheList=[]] 返回出去的结果
* @return {*}
* @memberof ArrayTool
* @example
* listChunk([1, 2, 3, 4, 5, 6, 7, 8, 9]) // [[1], [2], [3], [4], [5], [6], [7], [8], [9]]
* listChunk([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
* listChunk([1, 2, 3, 4, 5, 6, 7, 8, 9], 0) // []
* listChunk([1, 2, 3, 4, 5, 6, 7, 8, 9], -1) // []
*/
listChunk(list, size = 1, cacheList = []) {
var tmp = [...list]
if (size <= 0) {
return cacheList
}
while (tmp.length) {
cacheList.push(tmp.splice(0, size)) // 因为split会改变原数组
}
return cacheList
}
/**
* @description 简单的深拷贝
*
* @param {Object/Array} obj 目标数组
* @return {Object/Array} 目标数组Copy
* @memberof ArrayTool
* @example
* const person={
* name:'xiaoming',
* child:{
* name:'Jack',
* eat: function(){
* console.log('阿巴');
* }
* }
* }
*
* let personCopy = deepCopy(person)
* personCopy.child.eat = []
* console.log(person.child.eat); // fn()
* console.log(personCopy.child.eat); // []
* // 确实两个对象值引用不相同了
*/
deepCopy(obj) {
if (typeof obj != "object" || obj == null) {
return obj
}
return JSON.parse(JSON.stringify(obj))
}
/**
* @description 数组去重
* @description 原理:利用Set中不能出现重复元素的特征
* @param {Array} arr 目标数组
* @return {Array}
* @memberof ArrayTool
* @example
* uniqueArray([undefined, null, null, 1, 1]) // [undefined, null, 1]
*/
uniqueArray(arr) {
if (!Array.isArray(arr)) {
throw new Error("The first paramter must be an array")
}
if (arr.length == 1) {
return arr
}
return [...new Set(arr)]
}
/**
* @description 利用属性名比较
* @description localeCompare对字符串进行排序
* @description 思考:对每一个数组进行特定排序(localeCompare),因为数组元素可能位置不同但元素内容相同,所以必须按照某一顺序对其进行排序,这里按首字母对字符串进行排序
* @description 排完序用Object.values属性名唯一性进行去重
* @description 对多维数组(矩阵)去重(方法一)
*
* @param {Array} arr
* @return {Array}
* @memberof ArrayTool
* @example
* uniqueMultiDimensionalArray([
* ["你的", "我", "它"],
* ["我", "你的", "它"],
* ["一", "二", "三"],
* ["三", "二", "一"],
* ["你d", "a", "它"],
* ["a", "你d", "它"],
* ["one", "two", "three"],
* ["three", "two", "one"]
* ]) // [ ["你的", "它", "我"], ["二", "三", "一"], ["你d", "它", "a"], ["one", "three", "two"] ]
*/
uniqueMultiDimensionalArray(arr) {
let res = {}
// 进行排序
arr.map((item) => {
item.sort((a, b) => a.localeCompare(b))
res[item] = item
})
return Object.values(res)
}
/**
* @description 利用ES6语法中的Set,Set中的每个值都是唯一的
* @description 先将数组按字符串排序,顺便将其多维数组转成字符串(方便使用Set方法进行去重),Set去重后,使用逗号切割成数组
* @description 对多维数组(矩阵)去重(方法二)
*
* @param {Array} arr
* @return {Array}
* @memberof ArrayTool
* @example
* uniqueMultiDimensionalArray1([
* ["你的", "我", "它"],
* ["我", "你的", "它"],
* ["一", "二", "三"],
* ["三", "二", "一"],
* ["你d", "a", "它"],
* ["a", "你d", "它"],
* ["one", "two", "three"],
* ["three", "two", "one"]
* ]) // [ ["你的", "它", "我"], ["二", "三", "一"], ["你d", "它", "a"], ["one", "three", "two"] ]
*/
uniqueMultiDimensionalArray1(arr) {
let res = []
// 进行排序
arr.map((item) => {
res.push(item.sort((a, b) => a.localeCompare(b)).toString())
})
// console.log(res); // ["你的,它,我", "你的,它,我", "二,三,一", "二,三,一", "你d,它,a", "你d,它,a", "one,three,two", "one,three,two"]
// return Array.from(new Set(res)).map(item => item.split(','))
return [...new Set(res)].map((item) => item.split(",")) // 上下等价
}
/**
* @description 布尔全等判断
* @description 判断arr数组中,满足fn,如果全部都满足即为true;如果有一个不满足条件即为false
*
* @param {Array} arr
* @param {Function} [fn=Boolean]
* @return {Boolean}
* @memberof ArrayTool
* @example
* booleanAll([4, 2, 3], x => x > 1); // true
* booleanAll([4, 2, 3], x => x > 2); // false
*/
booleanAll(arr, fn = Boolean) {
return arr.every(fn)
}
/**
* @description 检查数组各项相等
*
* @param {Array} arr
* @return {Boolean}
* @memberof ArrayTool
* @example
* allEqual([1, 1, 1]); // true
* allEqual([1, 2, 3, 4, 5, 6]); // false
*/
allEqual(arr) {
return arr.every((val) => val === arr[0])
}
/**
* @description 判断两个数字约等于
*
* @param {Number} v1
* @param {Number} v2
* @param {number} [epsilon=0.001] 范围
* @return {Boolean}
* @memberof ArrayTool
* @example
* approximatelyEqual(Math.PI / 2.0, 1.5708, 0.001); // true
* approximatelyEqual(Math.PI / 2.0, 2.5708, 0.002); // false
*/
approximatelyEqual(v1, v2, epsilon = 0.001) {
return Math.abs(v1 - v2) < epsilon
}
/**
* @description 可以根据每个元素返回的值,使用reduce() 和push() 将元素添加到第二次参数fn中
* @description 利用reduce,第一个参数acc就是每次循环处理后将值存在reduce的第二个参数[[], []]里面,第一次循环从[[], []]拿出来(第一次是空的);
* @description 于是开始赋值filter[i]如果是true,就存放在第二个参数里面的第一个数组;如果是false则存在在第二个参数里面的第二个数组,累加过程的
* @description 拆分断言后的数组
*
* @param {*} arr
* @param {*} filter
* @return {*}
* @memberof ArrayTool
* @example
* bifurcate(['beep', 'boop', 'foo', 'bar'], [true, true, false, true]) // [ ['beep', 'boop', 'bar'], ['foo'] ]
*/
bifurcate(arr, filter) {
return arr.reduce(
(acc, val, i) => {
// console.log(acc, val, i)
return acc[filter[i] ? 0 : 1].push(val), acc
},
[[], []]
)
}
/**
* @description 其它类型转数组
*
* @param {*} val Array/Number/Boolean/String
* @return {Array}
* @memberof ArrayTool
* @example
* castArray(1); // [1]
* castArray([1]); // [1]
* castArray("消息No One Time跟我说"); // ["消息No One Time跟我说"]
*/
castArray(val) {
return Array.isArray(val) ? val : [val]
}
/**
* @description 去除数组中的无效/无用值
*
* @param {Array} arr 目标数组
* @return {Array}
* @memberof ArrayTool
* @example
* compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]) // [1, 2, 3, "a", "s", 34]
*/
compact(arr) {
return arr.filter(Boolean)
}
/**
* @description 还可以使用arr.flat(Infinity)
* @description 递归扁平化数组
*
* @param {Array} arr 目标数组
* @return {Array} 一维数组
* @memberof ArrayTool
* @example
* deepFlatten([1, [2], [[3], 4], 5]) // [1, 2, 3, 4, 5]
*/
deepFlatten(arr) {
return [].concat(
...arr.map((v) => (Array.isArray(v) ? this.deepFlatten(v) : v))
)
}
/**
* @description depth写999,也不会循环很多次,因为只要判断不是数组,他就不会继续递归下去
* @description 指定深度扁平化数组
*
* @param {Array} arr 目标数组
* @param {number} [depth=1] 指定深度
* @return {Array}
* @memberof ArrayTool
* @example
* flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
* flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]
* flatten([1, [2, [3, [4, 5], 6], 7], 8], 3); // [1, 2, 3, 4, 5, 6, 7, 8]
*/
flatten(arr, depth = 1) {
return arr.reduce((a, v) => {
return a.concat(
depth > 1 && Array.isArray(v) ? this.flatten(v, depth - 1) : v
)
}, [])
}
/**
* @description has() 方法返回一个布尔值来指示对应的值value是否存在Set对象中。
* @description 寻找差异(查找两个数组之间的差异,并返回第一个数组独有的)
*
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
* @memberof ArrayTool
* @example
* difference([1, 2, 3], [1, 5, 4]) // [2, 3]
*/
difference(arr1, arr2) {
const s = new Set(arr2)
return arr1.filter((a1) => !s.has(a1))
}
/**
* @description has() 方法返回一个布尔值来指示对应的值value是否存在Set对象中。
* @description 先执行再寻找差异(将给定函数应用于两个列表的每个元素之后,此方法返回两个数组之间的差异)
*
* @param {Array} arr1
* @param {Array} arr2
* @param {Function} fn 给定函数
* @return {Array}
* @memberof ArrayTool
* @example
* differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor) // [1.2]
* differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], v => v.x) // [ { x: 2 } ]
*/
differenceBy(arr1, arr2, fn) {
const s = new Set(arr2.map(fn))
return arr1.filter((a1) => !s.has(fn(a1)))
}
/**
* @description 从数组顶部开始删除元素,直到传递的函数返回为true
* @description 删除不符合条件的值
*
* @param {Array} arr
* @param {Function} func
* @return {Array}
* @memberof ArrayTool
* @example
* dropWhile([1, 2,3, 4], n => n >= 3) // [3, 4]
*
* 只要第一个返回true就不判断后面的了
* dropWhile([1, 3,2,1, 4], n => n >= 3) // [3, 2, 1, 4]
*/
dropWhile(arr, func) {
while (arr.length > 0 && !func(arr[0])) {
arr = arr.slice(1)
}
return arr
}
/**
* @description 返回数组中某值的所有索引(如果此值中未包含该值,则返回一个空数组)
*
* @param {Array} arr 目标数组
* @param {String} val 某值
* @return {Array}
* @memberof ArrayTool
* @example
* indexOfAll([1, 2, 3, 1, 2, 3], 1) // [0,3]
* indexOfAll([1, 2, 3], 4) // []
*/
indexOfAll(arr, val) {
return arr.reduce((acc, el, i) => {
return el === val ? [...acc, i] : acc
}, [])
}
/**
* @description 返回指定长度的升序/降序数组
*
* @param {Array} arr 目标数组
* @param {number} [n=1]
* @param {string} [sort="asc"] asc:升序;desc:降序
* @return {Array}
* @memberof ArrayTool
* @example
* minN([1, 2, 3]) // [1]
* minN([1, 2, 3], 2) // [1, 2]
* minN([1, 2, 4, 3], 3, 'asc') // [1, 2, 3]
* minN([1, 2, 4, 3], 3, 'desc') // [4, 3, 2]
*/
minN(arr, n = 1, sort = "asc") {
return [...arr]
.sort((a, b) => (sort === "desc" ? b - a : a - b))
.slice(0, n)
}
/**
* @description 根据条件反向筛选
*
* @param {Function} func
* @return {Array}
* @memberof ArrayTool
* @example
* [1, 2, 3, 4, 5, 6].filter(arrayTool.negate((n => n % 2 === 0))); // [ 1, 3, 5 ]
*/
negate(func) {
return (...args) => !func(...args)
}
/**
* @description 后面加min,整体值就肯定比min最小值要大,自然不小于最小值
* @description Math.random() * (max - min + 1)) 最大值减去最小值 + 1去乘Math.random()随机数0-1的数,那么就是两个差值的随机数;再去加最小值,算起来肯定不会超出最大值
* @description 生成两数之间指定长度的随机数组
*
* @param {number} min 最小值
* @param {number} max 最大值
* @param {number} [n=1] 返回数组的长度
* @return {Array}
* @memberof ArrayTool
* @example
* arrayTool.randomIntArrayInRange(10,20,10); // [11, 12, 10, 15, 18, 12, 15, 16, 13, 15]
* arrayTool.randomIntArrayInRange(10,10,10); // [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
*/
randomIntArrayInRange(min, max, n = 1) {
return Array.from(
{ length: n },
() => Math.floor(Math.random() * (max - min + 1)) + min
)
}
/**
* @description Math.random() * arr.length 肯定在数组长度的范围内
* @description 在指定数组中获取随机数
*
* @param {Array} arr 目标数组
* @return {Array}
* @memberof ArrayTool
* @example
* sample([1,5,8,9,10]); // 10
* sample([1,5,8,9,10]); // 5
* sample([1,5,8,9,10]); // 1
*/
sample(arr) {
return arr[Math.floor(Math.random() * arr.length)]
}
/**
* @description 此代码段可用于从数组中获取指定长度的随机数,直至穷尽数组。 使用Fisher-Yates算法对数组中的元素进行随机选择。
* @description 费雪耶兹(Fisher–Yates) 也被称作高纳德( Knuth)随机置乱算法(https://blog.csdn.net/lhkaikai/article/details/25627161)
* @description 原理:比如:arr = [1,2,3,4,5,6,7,8]
* @description 第一轮:从1到8中随机选择一个数,得到6,则交换当前数组中第8和第6个数,则:[1,2,3,4,5,8,7] // ,6
* @description 第二论:从1到7中随机选择一个数,得到2,则交换当前数组中第7和第2个数,则:[1,7,3,4,5,8] // ,2,6
* @description 下一个随机数从1到6中摇出,刚好是6,这意味着只需把当前线性表中的第6个数留在原位置,接着进行下一步;以此类推,直到整个排列完成。
* @description 第三论:从1到6中随机选择一个数,得到6,则交换当前数组中第6和第6个数,则:[1,7,3,4,5] // ,8,2,6
* @description 第四论:从1到5中随机选择一个数,得到1,则交换当前数组中第5和第1个数,则:[5,7,3,4] // ,1,8,2,6
* @description 第五论:从1到4中随机选择一个数,得到3,则交换当前数组中第4和第3个数,则:[5,7,4] // ,3,1,8,2,6
* @description 第六论:从1到3中随机选择一个数,得到3,则交换当前数组中第3和第3个数,则:[5,7] // ,4,3,1,8,2,6
* @description 第七论:从1到2中随机选择一个数,得到1,则交换当前数组中第2和第1个数,则:[7] // ,5,4,3,1,8,2,6
* @description 截至目前,所有需要的置乱已经完成,所以最终的结果是:7 5 4 3 1 8 2 6
* @description 在指定数组中获取指定长度的随机数(“洗牌” 数组)
*
* @param {Array} [...arr]
* @param {number} [n=1]
* @return {Array}
* @memberof ArrayTool
* @example
* sampleSize([1, 2, 3,4], 2); // [4, 3]
* sampleSize([1, 2, 3,4], 2); // [1, 3]
* sampleSize([1, 2, 3,4], 2); // [1, 4]
*/
sampleSize([...arr], n = 1) {
let m = arr.length
while (m) {
const i = Math.floor(Math.random() * m--) // 随机选择一个数,逐渐递减
;[arr[m], arr[i]] = [arr[i], arr[m]] // 交换
}
return arr.slice(0, n)
}
/**
* @description 根据parent_id生成树结构(阿里一面真题)
*
* @param {Array} items 目标数组
* @param {*} [id=null] 根据特定字段作为id
* @param {string} [link="parent_id"] 父id
* @return {Array}
* @memberof ArrayTool
* @example
* const comments = [
* { id: 1, parent_id: null },
* { id: 2, parent_id: 1 },
* { id: 3, parent_id: 1 },
* { id: 4, parent_id: 2 },
* { id: 5, parent_id: 4 }
* ];
*
* nest(comments) // [{ id: 1, parent_id: null, children: [
* {
* id: 2,
* parent_id: 1,
* children: [
* {
* id:2,
* parent_id: 1,
* children: [{id: 4, parent_id: 2, children: [
* {id: 5, parent_id: 4, children: [{id: 5, parent_id: 4, children: [] }]}
* ]
* }]
* }
* ]
* },
* { id: 2, parent_id: 1, children: [] }
* ]}]
*/
nest(items, id = null, link = "parent_id") {
return items
.filter((item) => item[link] === id)
.map((itemM) => ({ ...itemM, children: this.nest(items, itemM.id) }))
}
/**
* @description 思路:去判断他们可能会相等的情况(数字、日期、非对象)、再去判断不相等的情况(原型、对象长度不一致直接返回false)
* @description 如果以上都满足,那就是对象数组类型了,通过every函数让对象里面的属性去做递归(只要有一个为false,那结果就为false)
*
* @description 在两个变量之间进行深度比较以确定它们是否全等。
* @description 此代码段精简的核心在于Array.prototype.every()的使用。
*
* @description 全等判断
*
* @param {*} a 目标变量a
* @param {*} b 目标变量b
* @return {Boolean}
* @memberof ArrayTool
* @example
* equals({ a: [2, { e: 3 }], b: [4], c: 'foo' }, { a: [2, { e: 3 }], b: [4], c: 'foo' }); // true
*/
equals(a, b) {
if (a === b) {
return true
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime()
}
if (!a || !b || (typeof a !== "object" && typeof b !== "object")) {
return a === b
}
if (a.prototype !== b.prototype) {
return false
}
let keys = Object.keys(a)
if (keys.length !== Object.keys(b).length) {
return false
}
return keys.every((k) => this.equals(a[k], b[k]))
}
}
export default ArrayTool