ES6的Set和Map數據結構,由你製造

你造嗎

每當去學習ES6的時候,我都會不禁自主的打開阮一峯老師的《ES6入門》去學習和查找用法,相信大多數同窗也都和我同樣看過阮老師寫的文章。es6

固然你們也都知道ES6裏經常使用的API就那麼些,很少很多,用在項目中也是剛恰好。算法

不過在每次讀到Set和Map數據結構那一章的時候,老是有點不知所措,由於我不明白實現這樣的數據結構,目的是什麼,意義又是什麼呢數組

Set和Map主要的應用場景在於數組去重數據存儲,幸運的是在讀了關於數據結構和算法之類的書籍後,恍然大悟的發現bash

原來Set是一種叫作集合的數據結構,Map是一種叫作字典的數據結構數據結構

那麼下面就跟隨我一塊兒去了解下這兩種數據結構,最後來親手實現的一個ES6中的Set和Map吧數據結構和算法

集合

  • 集合是由一組無序且惟一(即不能重複)的項組成的,能夠想象成集合是一個既沒有重複元素,也沒有順序概念的數組
  • ES6提供了新的數據結構Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值
  • Set 自己是一個構造函數,用來生成 Set 數據結構
  • 這裏說的Set其實就是咱們所要講到的集合,先來看下基礎用法
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);   // 2 3 5 4
}

// 去除數組的重複成員
let array = [1,2,1,4,5,3];
[...new Set(array)]     // [1, 2, 4, 5, 3]
複製代碼

具體用法若是還有不清楚的,這裏我會在後面一一細說。如今仍是來看一下以ES6中Set類(數據結構)爲基礎實現的集合吧函數

Set實例的屬性和方法

  • Set的屬性:
    • size:返回集合所包含元素的數量
  • Set的方法:
    • 操做方法
      • add(value):向集合添加一個新的項
      • delete(value):從集合中移除一個值
      • has(value):若是值在集合中存在,返回true,不然false
      • clear(): 移除集合裏全部的項
    • 遍歷方法
      • keys():返回一個包含集合中全部鍵的數組
      • values():返回一個包含集合中全部值的數組
      • entries:返回一個包含集合中全部鍵值對的數組(感受沒什麼用就不實現了)
      • forEach():用於對集合成員執行某種操做,沒有返回值

建立一個集合

function Set(arr = []) {    // 能夠傳入數組
    let items = {};
    this.size = 0;  // 記錄集合中成員的數量
}

module.exports = Set;
複製代碼

這裏用{}對象來表示集合,也是由於對象不容許一個鍵指向兩個不一樣的屬性,保證了集合裏的元素都是惟一的學習

接下來,就須要按照ES6中Set類的實現,添加一些集合的操做方法測試

has方法

首先要實現的是has方法,由於在add和delete等其餘方法中都會被調用,下面來看一下它的實現ui

function Set() {
    let items = {};
    this.size = 0;
    
    // has(val)方法
    this.has = function(val) {
        // 對象都有hasOwnProperty方法,判斷是否擁有特定屬性
        return items.hasOwnProperty(val);  
    };
}
複製代碼

add方法

接下來要實現add方法

// add(val)方法
    this.add = function(val) {
        if (!this.has(val)) {
            items[val] = val;
            this.size++;    // 累加集合成員數量
            return true;
        }
        return false;
    };
複製代碼

對於給定的val,能夠檢測是否存在於集合中

  • 若是不存在,就添加到集合中,返回true
  • 若是存在,就直接返回false,不作任何操做

delete和clear方法

繼續寫着,這回把兩個都寫上

// delete(val)方法
    this.delete = function(val) {
        if (this.has(val)) {
            delete items[val];  // 將items對象上的屬性刪掉
            this.size--;
            return true;
        }
        return false;
    };
    // clear方法
    this.clear = function() {
        items = {};     // 直接將集合賦一個空對象便可
        this.size = 0;
    };
複製代碼

在delete方法中,判斷val是否存在於集合中,若是存在就直接從集合中刪掉,返回true

以上完成的都是操做方法,下面咱們再來實現一下遍歷方法

keys、values方法

這兩個方法咱們能夠放在一塊兒來實現,由於經過ES6對Object的擴展能夠輕鬆實現對應的方法,下面看一下具體實現,上代碼:

// keys()方法
    this.keys = function() {
        return Object.keys(items);  // 返回遍歷集合的全部鍵名的數組
    };
    // values()方法
    this.values = function() {
        return Object.values(items);  // 返回遍歷集合的全部鍵值的數組
    };
複製代碼

使用一下看看

// set.js
const Set = require('./Set.js');    // 導入寫好的Set類
let set = new Set();
set.add(1);
set.add(3);
set.add(2);
console.log(set.keys());    // [ '1', '2', '3' ]
console.log(set.values());  // [ 1, 2, 3 ]
複製代碼

這裏咱們看到和ES6中的Set有點區別,由於Object的這幾個方法都是按照數值大小,從小到大遍歷的數組,因此你們知道這一點比較好,具體實現仍是有些不一樣的,哈哈

forEach方法

ES6中Set結構的實例上帶的forEach方法,其實和數組的forEach方法很類似,只不過Set結構的鍵名就是鍵值,因此第一個參數與第二個參數的值永遠都是同樣的

下面就按照實現數組的forEach方法,咱們來完成Set的forEach方法

// forEach(fn, context)方法
    this.forEach = function(fn, context = this) {
        for (let i = 0; i < this.size; i++) {
            let item = Object.keys(items)[i];
            fn.call(context, item, item, items);     
        }
    };
複製代碼

使用forEach方法

// set.js
const Set = require('./Set.js');
let set = new Set();
set.add(1);
set.add(4);
set.add('3');
set.forEach((value, key) => console.log(key + ' : ' + value));  // 1:1, 3:3, 4:4
let arr = set.values();     // [ 1, 3, 4 ]
arr = new Set(arr.map(x => x * 2)).values();
console.log(arr);           // [ 2, 6, 8 ]
複製代碼

基本上實現了Set結構的方法,不過,發現一個問題,那就是每次添加一個元素都要add這樣寫起來確實好麻煩,Set是能夠接收一個數組做爲參數的,那麼咱們把這個也實現一下

function Set(arr = []) {    // 傳入接受的數組,若是沒有傳指定一個空數組作爲初始值
    let items = {};
    this.size = 0;
    // has方法
    this.has = function (val) {
        return items.hasOwnProperty(val);
    };
    // add方法
    this.add = function (val) {
        // 若是沒有存在items裏面就能夠直接寫入
        if (!this.has(val)) {
            items[val] = val;
            this.size++;
            return true;
        }
        return false;
    };
    arr.forEach((val, i) => {   // 遍歷傳入的數組
        this.add(val);          // 將數組裏每一項值添加到集合中
    });
    // 省略...
}
複製代碼

再來看看如今能不能支持傳入的數組了

// 間接使用map和filter
const Set = require('./Set.js');
let arr = new Set([1, 2, 3]).values();
m = new Set(arr.map(x => x * 2));
f = new Set(arr.filter(x => x>1));
console.log(m.values());    // [ 2, 4, 6 ]
console.log(f.values());    // [ 2, 3 ]

// 數組去重
let arr2 = new Set([3, 5, 2, 1, 2, 5, 5]).values();
console.log(arr2);  // [ 1, 2, 3, 5 ]
複製代碼

如今咱們有了一個和ES6中很是相似的Set類實現。如前所述,也能夠用數組替代對象,存儲元素。喜歡動手的同窗們,以後也能夠去嘗試一下

除此以外,Set還能夠實現並集(union),交集(intersect),差集(difference)

作事仍是要作全套的,咱們也一一來實現一下吧

union並集和intersect交集

  • 並集的數學概念,集合A和集合B的並集,表示爲A∪B
  • 交集的數學概念,集合A和集合B的交集,表示爲A∩B

如圖所示:

如今先來實現union方法

// 並集
    this.union = function (other) {
        let union = new Set();
        let values = this.values();
        for (let i = 0; i < values.length; i++) {
            union.add(values[i]);
        }
        values = other.values();    // 將values從新賦值爲新的集合
        for (let i = 0; i < values.length; i++) {
            union.add(values[i]);
        }

        return union;
    };
    // 交集
    this.intersect = function (other) {
        let intersect = new Set();
        let values = this.values();
        for (let i = 0; i < values.length; i++) {
            if (other.has(values[i])) {     // 查看是否也存在於other中
                intersect.add(values[i]);   // 存在的話就像intersect中添加元素
            }
        }
        return intersect;
    };
複製代碼

再來看下difference差集的實現,以後一塊兒再測試一番

difference差集

  • 差集的數學概念,集合A和集合B的差集,表示爲A-B

// 差集
    this.difference = function (other) {
        let difference = new Set();
        let values = this.values();
        for (let i = 0; i < values.length; i++) {
            if (!other.has(values[i])) {    // 將不存在於other集合中的添加到新的集合中
                difference.add(values[i]);
            }
        }
        return difference;
    };
複製代碼

Set完整實現

在此,先給你們貼一下完整的實現代碼

function Set(arr = []) {
    let items = {};
    this.size = 0;
    // has方法
    this.has = function (val) {
        return items.hasOwnProperty(val);
    };
    // add方法
    this.add = function (val) {
        // 若是沒有存在items裏面就能夠直接寫入
        if (!this.has(val)) {
            items[val] = val;
            this.size++;
            return true;
        }
        return false;
    };
    arr.forEach((val, i) => {
        this.add(val);
    });
    // delete方法
    this.delete = function (val) {
        if (this.has(val)) {
            delete items[val];  // 將items對象上的屬性刪掉
            this.size--;
            return true;
        }
        return false;
    };
    // clear方法
    this.clear = function () {
        items = {};
        this.size = 0;
    };
    // keys方法
    this.keys = function () {
        return Object.keys(items);
    };
    // values方法
    this.values = function () {
        return Object.values(items);
    }
    // forEach方法
    this.forEach = function (fn, context = this) {
        for (let i = 0; i < this.size; i++) {
            let item = Object.keys(items)[i];
            fn.call(context, item, item, items);
        }
    }

    // 並集
    this.union = function (other) {
        let union = new Set();
        let values = this.values();

        for (let i = 0; i < values.length; i++) {
            union.add(values[i]);
        }
        values = other.values();    // 將values從新賦值爲新的集合
        for (let i = 0; i < values.length; i++) {
            union.add(values[i]);
        }

        return union;
    };
    // 交集
    this.intersect = function (other) {
        let intersect = new Set();
        let values = this.values();
        for (let i = 0; i < values.length; i++) {
            if (other.has(values[i])) {
                intersect.add(values[i]);
            }
        }
        return intersect;
    };
    // 差集
    this.difference = function (other) {
        let difference = new Set();
        let values = this.values();
        for (let i = 0; i < values.length; i++) {
            if (!other.has(values[i])) {
                difference.add(values[i]);
            }
        }
        return difference;
    };
    // 子集
    this.subset = function(other) {
        if (this.size > other.size) {
            return false;
        } else {
            let values = this.values();
            for (let i = 0; i < values.length; i++) {
                console.log(values[i])
                console.log(other.values())
                if (!other.has(values[i])) {
                    return false;
                }
            }
            return true;
        }
    };
}

module.exports = Set;
複製代碼

寫了辣麼多一塊兒來測試一下吧

const Set = require('./Set.js');
let set = new Set([2, 1, 3]);
console.log(set.keys());    // [ '1', '2', '3' ]
console.log(set.values());  // [ 1, 2, 3 ]
console.log(set.size);      // 3
set.delete(1);
console.log(set.values());  // [ 2, 3 ]
set.clear();
console.log(set.size);      // 0

// 並集
let a = [1, 2, 3];
let b = new Set([4, 3, 2]);
let union = new Set(a).union(b).values();
console.log(union);     // [ 1, 2, 3, 4 ]

// 交集
let c = new Set([4, 3, 2]);
let intersect = new Set([1,2,3]).intersect(c).values();
console.log(intersect); // [ 2, 3 ]

// 差集
let d = new Set([4, 3, 2]);
let difference = new Set([1,2,3]).difference(d).values();
// [1,2,3]和[4,3,2]的差集是1
console.log(difference);    // [ 1 ]
複製代碼

目前爲止咱們用集合這種數據結構就實現了相似ES6中Set類,上面的使用方法也基本同樣,你們能夠以後有時間的話動手去敲一敲看一看,走過路過不能錯過

既然咱們已經完成了Set的實現,那麼好事要成雙,一氣呵成再把Map也一塊兒寫出來,天了擼的,開始

字典

在數據結構還有一種結構叫作字典,它就是實現基於ES6中的Map類的結構

那麼集合又和字典有什麼區別呢:

  • 共同點:集合、字典能夠存儲不重複的值
  • 不一樣點:集合是以[值,值]的形式存儲元素,字典是以[鍵,值]的形式存儲

因此這一下讓咱們明白了,Map其實的主要用途也是用於存儲數據的,相比於Object只提供「字符串—值」的對應,Map提供了「值—值」的對應。也就是說若是你須要「鍵值對」的數據結構,Map比Object更合適

下面來看一下基本使用:

const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false
複製代碼

以上是Map的基本使用,還有更多有用的方法稍後會隨着實現的深刻分別展現

Map的屬性和方法

屬性:

  • size:返回字典所包含的元素個數

操做方法:

  • set(key, val): 向字典中添加新元素
  • get(key):經過鍵值查找特定的數值並返回
  • has(key):若是鍵存在字典中返回true,不然false
  • delete(key): 經過鍵值從字典中移除對應的數據
  • clear():將這個字典中的全部元素刪除

遍歷方法:

  • keys():將字典中包含的全部鍵名以數組形式返回
  • values():將字典中包含的全部數值以數組形式返回
  • forEach():遍歷字典的全部成員

知道了都有哪些屬性和方法,那就閒言少敘,開始建立一個字典吧

建立一個字典

function Map() {
    let items = {};
}

module.exports = Map;   // 導出
複製代碼

建立好了字典這個骨架,那就開始添加一些方法了

has方法

首當其衝的固然是has了,由於在set和get裏都會用到,實現思路和以前寫的集合也很相似

function Map() {
    let items = {};
    // has(key)方法
    this.has = function(val) {
        return items.hasOwnProperty(val);
    };
}
複製代碼

實現了has方法後,咱們能夠來判斷字典中是否包含該屬性了,繼續來實現其餘方法

set和get方法

// set(key, val)方法
    // set相同key時,後面聲明的會覆蓋前面
    // 如: new Map().set({}, 'a')
    this.set = function(key, val) {
        items[key] = val;   
    };
    // get(key)方法
    this.get = function(key) {
        // 判斷是否有key,若是有的話直接返回對應的值
        // 若是讀取一個未知的鍵,則返回undefined
        return this.has(key) ? items[key] : undefined;
    };
複製代碼

set和get方法寫好了,再接着搞delete和clear方法,不廢話,看

delete和clear方法

// delete(key)方法
    this.delete = function(key) {
        if (this.has(key)) {    // 若是有key值
            delete items[key];  // 直接刪掉items上對應的屬性
            this.size--;        // 讓size總數減1
            return true;
        }
        return false;
    };
    // clear()方法
    this.clear = function() {
        items = {};
        this.size = 0;
    };
複製代碼

上面把屬性和操做方法都分別完成了,還剩下最後的遍歷方法了,繼續寫下去,堅持到底就是勝利,各位看官也不容易了,加油加油!!!

遍歷方法(keys,values,forEach)

// keys()方法
    this.keys = function() {
        return Object.keys(items);
    };
    // values()方法
    this.values = function() {
        return Object.values(items);  
    };
    // forEach(fn, context)方法
    this.forEach = function(fn, context = this) {
        for (let i = 0; i < this.size; i++) {
            let key = Object.keys(items)[i];
            let value = Object.values(items)[i];
            fn.call(context, value, key, items);
        }
    };
複製代碼

Now終於完成了Map類的實現,我給你們貼一下完整代碼和測試用例,供你們空閒時間來研究分析分析

Map完整實現

function Map() {
    let items = {};
    this.size = 0;

    // 操做方法
    // has方法
    this.has = function(val) {
        return items.hasOwnProperty(val);
    };
    // set(key, val)方法
    this.set = function(key, val) {
        items[key] = val;
        this.size++;
    };
    // get(key)方法
    this.get = function(key) {
        return this.has(key) ? items[key] : undefined;
    };
    // delete(key)方法
    this.delete = function(key) {
        if (this.has(key)) {
            delete items[key];
            this.size--;
            return true;
        }
        return false;
    };
    // clear()方法
    this.clear = function() {
        items = {};
        this.size = 0;
    };
    // 遍歷方法
    // keys()方法
    this.keys = function() {
        return Object.keys(items);
    };
    // values()方法
    this.values = function() {
        return Object.values(items);
    };
    // forEach(fn, context)方法
    this.forEach = function(fn, context = this) {
        for (let i = 0; i < this.size; i++) {
            let key = Object.keys(items)[i];
            let value = Object.values(items)[i];
            fn.call(context, value, key, items);
        }
    };
}

module.exports = Map;
複製代碼

再來看看下面的測試栗子

// map.js
    // 使用Map類
    const Map = require('./Map.js');
    let m = new Map();
    m.set('Jay', 'Jay的Chou');
    m.set(true, '真的');
    console.log(m.has('Chou'));  // false
    console.log(m.size);        // 2
    console.log(m.keys());      // [ 'Jay', 'true' ]
    console.log(m.values());    // [ 'Jay的Chou', '真的' ]
    console.log(m.get('jay'));  // undefined
    
    m.delete(true);
    console.log(m.keys());      // [ 'Jay' ]
    console.log(m.values());    // [ 'Jay的Chou' ]
複製代碼

這不是總結

最後的戰役,作個非正式的總結吧

根據集合和字典這兩種數據結構實現了相似ES6的Set和Map數據結構。

但其實完成的還不完善,還有一些方法須要你們一塊兒再去思考一下

這裏我只是用集合和字典來給你們牽個頭,也但願你們看完後能夠集思廣益的去實現一下

OK,那麼感謝你們的觀看了,前面都是廢話,這纔是真心的,哈哈

相關文章
相關標籤/搜索