javascript 數組去重的6種思路

前端在平常開發中或多或少都會碰到有對數據去重的需求,實際上,像是lodash這些工具庫已經有成熟完備的實現,而且能夠成熟地運用於生產環境。可是這並不妨礙咱們從思惟拓展的角度出發,看看去重能夠用幾種思路去實現。

首先是常規的雙層循環比對的思路實現前端

function doubleLoopUniq(arr) {
  let result = [];
  for (let i = 0, len = arr.length, isExist; i < len; i++) {
    // 定義一個變量表示當前元素在 result 中是否存在。
    isExist = false;
    for (let j = 0, rLen = result.length; j < rLen; j++) {
      if (result[j] === arr[i]) {
        // 依次對result 中的元素 和 原數組元素進行比對。
        isExist = true;
        break;
      }
    }
    // 最後判斷若是不存在,則將此元素插入result
    !isExist && result.push(arr[i]);
  }
  return result;
}

藉助 js內置的indexOf 進行去重node

function indexOfUniq(arr) {
  let result = [];
  for (let i = 0, len = arr.length; i < len; i++) {
    // 用indexOf 簡化了二層循環的流程
    if (result.indexOf(arr[i]) === -1) result.push(arr[i]);
  }
  return result;
}

排序後先後比對去重git

function sortUniq(arr) {
  let result = [], last;
  // 這裏解構是爲了避免對原數組產生反作用
  [ ...arr ].sort().forEach(item => {
    if (item != last) {
      result.push(item);
      last = item;
    }
  });
  return result;
}

經過hashTable去重github

function hashUniq(arr) {
  let hashTable = arr.reduce((result, curr, index, array) => {
    result[curr] = true;
    return result;
  }, {})
  return Object.keys(hashTable).map(item => parseInt(item, 10));
}

ES6 SET一行代碼實現去重數組

function toSetUniq(arr) {
  return Array.from(new Set(arr));
}

splice 去重(直接操做數組自己,帶反作用)app

function inPlaceUniq(arr) {
  let idx = 0;
  while (idx < arr.length) {
    let compare = idx + 1;
    while (compare < arr.length) {
      if (arr[idx] == arr[compare]) {
        arr.splice(compare, 1);
        continue;
      }
      ++compare
    }
    ++idx;
  }
  return arr;
}

最後在nodejs下面簡單跑個測試,看看哪一個效率高~dom

let data = [];
for (var i = 0; i < 100000; i++) {
  data.push(Math.random())
}

// 實現一個性能測試的裝飾器
function performanceTest(fn, descript) {
  var a = new Date().getTime();
  return function () {
    fn.apply(this, [].slice.call(arguments, 0));
    console.log(descript, new Date().getTime() - a)
  }
}

performanceTest(hashUniq, "hashTable")(data)
performanceTest(sortUniq, "sortUniq")(data)
performanceTest(toSetUniq, "toSetUniq")(data)
performanceTest(indexOfUniq, "indexOfUniq")(data)
performanceTest(doubleLoopUniq, "doubleLoopUniq")(data)
performanceTest(inPlaceUniq, "inPlaceUniq")(data)

結果以下工具

hashTable 168ms
sortUniq 332ms
toSetUniq 80ms
indexOfUniq 4280ms
doubleLoopUniq 13303ms
inPlaceUniq 9977ms

延伸思考: 若是數組內的元素是對象該怎麼去重呢?oop

既然是引用類型,那麼難免會使用到deepEqual,當然這種思路能夠解答這道問題,但不免不夠高效。性能

從上面的測試中也可見經過new Set 和 hashTable 去重是最高效的。
因此毫無疑問,咱們要基於這兩種方式去改造,我想用的是hashTable,
另外一方面,爲了下降深度比較帶來的耗時,我嘗試用JSON.stringify 將引用類型轉化爲基本類型。

function collectionUniq(collection) {
  let hashTable = {};
  collection.forEach(item => {
    hashTable[JSON.stringify(item)] = true;
  })
  return Object.keys(hashTable).map(item => JSON.parse(item))
}

那麼問題來了,咱們都知道對象的屬性是無序的,假如數據是這種狀況,那就GG了。

let collection = [ { a: 1, b: 2, c: 3 }, { b: 2, c: 3, a: 1 } ]

有一種toHash的思路,在對這個數組進行一次基本的去重以後,爲了保證準確,
先遍歷JSON 字符串 =>
經過 charCodeAt()拿到每一個字符串 的 unicode 編碼 =>
相加獲得一個總數,最後再兩兩進行比較,數值相等的就是重複的,這樣就達到去重的效果了。

function toHash(obj) {
  let power = 1;
  let res = 0;
  const string = JSON.stringify(obj, null, 2);
  for (let i = 0, l = string.length; i < l; i++) {
    switch (string[i]) {
      case '{':
        power *= 2
        break
      case '}':
        power /= 2
        break
      case ' ':
      case '\n':
      case '\r':
      case '\t':
      break
      default:
        res += string[i].charCodeAt(0) * power
    }
  }
  return res
}

這只是一個實現基本的思路,有很大的改進空間,爲了減小hash碰撞的可能,能夠對一些特殊字符進行權重的增減。

重點是保證碰撞的概率小到比中大獎還小就能夠了。


2018.2.8
上面是一個比較清奇的思路,常規的作法,實際上仍是應該從優化深度比較的效率入手。
看到一個很好的實現思路,是一個優先判錯的思路,經過預設各類前置條件來避免高代價的循環,這種思路儘管在數據量小的時候由於前置判斷可能有一些微乎其微的性能損耗,可是數據量越大,優點就越明顯了。感興趣的能夠了解下。
https://github.com/epoberezki...

相關文章
相關標籤/搜索