# 数组类高频面试题

# 一.数组扁平化(5种解法)

拍平多维数组var arr = [1, 2, 3, [3, 3, 3, [5, 4, 5, 6, 6, 7, 8]],333, 4444]];

  • 递归处理
  • 闭包+递归处理
  • es6扩展运算符+reduce/concat+push+reduce
  • toString处理
  • rest参数

# 1. 递归处理

function flatten(arr){
  let child = []; //child变量在每次递归时都会清空。故每次遍历结束都需要返回
  for(let i of arr){
    if(Array.isArray(i)){
      child = [...child, ...flatten(i)] 
      //注意:flatten(i)之后的结果也需要展开,否则,只能复制数组,无法扁平化
    }else{
      child = [...child, i]
    }
  }
  return child;
}
function flatten(arr){
  let child = []
  for(let item of arr){
    if(Array.isArray(item)){  
      child = child.concat(flatten(item))
    }else{
      child.push(item)
    }
  }
  return child
}
console.log(flatten(arr))
//  concat方法不会改变原数组,join会
//  concat连接的不仅可以是具体的值,也可以是数组,当是数组时,添加的时数组中的值,该方法不改变原数组,而是返回一个连接后的副本,故使用concat,要把结果赋值给新变量
//  push会改写原数组,并返回新数组的长度,故不需要新变量承接

# 2. 闭包+递归处理

let product = () => {
  let child = [] //闭包中的child变量会一直在内存中,是一个累积变量
  let flatten = (arr) => {
    for(let item of arr){
      if(Array.isArray(item)){
        flatten(item) //此处的作用只是判断item为数组,就将它推入下一次递归
      }else{
        child.push(item) 
        //push会改变原数组,并返回新数组的长度,所有的数组元素都是在此处被push进child变量
      }
    }
    return child //每次都返回一个累积后的child变量
  }
  return flatten
}
let f = product()
console.log(f(arr2))

# 3. es6扩展运算符+reduce

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

# 4. toString()

function flatten(arr){
  return arr.toString().split(',').map(item=> +item)
}
// 数组的toString()方法将数组转化为其字符串形式, split方法将字符串用指定分隔符切成数组,+item将字符串转化为number类型
//该方法无法处理数组成员为非数组的情况,最终会被转化为NaN

# 5. rest参数

function flatten(arr){
  while(arr.some(item => Array.isArray(item))){
    arr = [].concat(...arr)
  }
  return arr
}

// rest参数默认只能展开一层,扩展运算符不会修改原数组

# 二.数组去重的7种姿势

数组去重var arr = [1,2,2,3,4,5,6,4]

  • 双重循环去重
  • filter+indexOf去重
  • forEach+includes去重
  • reduce+sort去重
  • 对象键值对去重
  • Map去重
  • Set去重

# 最优的数组去重算法是采用Map数据结构实现的算法

# 1. 双重循环去重

function unique(arr){
  let len = arr.length;
  for(let i=0; i<len;i++){
    for(let j=i+1; j<len;j++){
      if(arr[i]===arr[j]){
        // splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一
        arr.splice(j,1)
        len--
        j--
      }
    }
  }
  return arr
}
console.log(unique(arr))

# 2. filter+indexOf去重

Array.prototype.unique = function(){
  return this.filter((item, index) => {
    return this.indexOf(item) === index
  })
}

# 3. forEach+includes去重

Array.prototype.unique = function () {
  const newArray = [];
  this.forEach(item => {
    if (!newArray.includes(item)) {
      newArray.push(item);
    }
  });
  return newArray;
}

# 4. reduce+sort去重

Array.prototype.unique = function(arr){
  return this.sort().reduce((init, cur) => {
    if(init.length === 0 || init[init.length-1] !== cur){
      init.push(cur)
    } 
    return init 
  }, [])
}

# 5. 对象键值对去重

  • 利用了对象的key不可以重复的特性来进行去重
  • 但需要注意:
    • 无法区分隐式类型转换成字符串后一样的值,比如 1 和 '1'
    • 无法处理复杂数据类型,比如对象(因为对象作为 key 会变成 [object Object])
    • 特殊数据,比如 'proto',因为对象的 proto 属性无法被重写
function Person(){
  this.a = 1
}
const p = new Person()
let arr2 = [1,'1',true,{a: 1},p, 2,3,2,4,4]
function unique(arr){
  let ret = []
  let obj = {}
  for(let item of arr){
    let tmp
    if(typeof item === 'object'){
      let constructor = Object.getPrototypeOf(item).constructor
      tmp = JSON.stringify(item) + constructor
    }else{
      tmp = typeof item + JSON.stringify(item)
    }
    if(!obj[tmp]){
      obj[tmp] = 1
      ret.push(item)
    }
  }
  return ret
}
console.log(unique(arr2), arr2)

# 6. filter+Map去重

function unique(arr){
  let map = new Map()
  return arr.filter(item => {
    // if(!map.has(item)){
    //   map.set(item,1)
    //   return item
    // }
    return !map.has(item) && map.set(item,1)
  })
}

# 7. Set去重

function unique(arr){
  let set = new Set(arr)
  return Array.from(set)
  // return [...set]
}

# 三.将类数组对象转为数组(4种)

  • 如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)
  • 典型的“类似数组的对象”:
    • 函数的arguments对象,
    • 以及大多数 DOM 元素集,
    • 还有字符串

# 将类数组转化为真正的数组

  • es6新增的扩展运算符(...)
  • es6的Array.from()方法
  • es6的for...of+push
  • es5的slice
let divEle = document.querySelectorAll('div')
let divArr = [];
for(let item of divEle) {
    divArr.push(item)
}

let divArr = [...divEle];

let divArr2 = Array.from(divEle)

var divArr2 = Array.prototype.slice.call(divEle);

let divArr = [].concat.apply([], divEle) //concat不仅会将类数组对象转为数组,还会将数组展开

# 四.洗牌算法

  • 它要求数组中的数打乱后,每个数出现在任意位置的概率相同
function shuffle(arr){
  for(let i=0,len=arr.length;i<len;i++){ //缓存一下数组长度
    const j =  Math.floor(Math.random()*(len-i))
    //返回一个0到len-i之间的随机整数[0,len-i-1]
    let tmp = arr[j]
    arr[j] = arr[len-i-1]
    arr[len-i-1] = tmp
  }
  return arr
}
var arr = [1, 3, 5, 7, 9]
shuffle(arr)
  • 错误解法:
function shuffle(arr){
  return arr.sort(()=> Math.random()-0.5)
}
// 两数交换的概率为50%,
//这是为什么呢?问题就出在sort这个API上,对于chrome浏览器而言
//当数组长度在10以内时,sort()采用插入排序,反之,则混合使用快速排序和插入排序,
//这样会导致选取的两个交换位置的数不随机,导致数组也就没有真正打乱

# 五.随机数获取

  • 获取一个[0,1)之间的随机数
Math.random()
  • 获取一个两数之间的随机数[min, max)
Math.random()*(max-min)+min
  • 获取一个两数之间的随机整数[min, max)
function getRandomInt(min, max){
  min=Math.ceil(min);
  max=Math.floor(max);
  return Math.floor(Math.random()*(max-min))+min;
}
  • 获取一个两数之间的随机整数,包括两个数在内[min, max]
function getRandomInt(min, max){
  min=Math.ceil(min)
  max=Math.floor(max);
  return Math.floor(Math.random()*(max-min+1))+min;//含最大值,含最小值
}