JavaScript数据结构——集合的实现与应用

  与数学中的集合概念相似,集合由一组无序的元素组成,且集合中的每一个元素都是惟一存在的。能够回顾一下中学数学中集合的概念,咱们这里所要定义的集合也具备空集(即集合的内容为空)、交集、并集、差集、子集的特性。数组

  在ES6中,原生的Set类已经实现了集合的所有特性,稍后咱们会介绍它的用法。函数

  咱们使用JavaSctipt的对象来表示集合,下面是集合类的主要实现方法:测试

class Set {
    constructor () {
        this.items = {};
    }

    add (value) { // 向集合中添加元素
        if (!this.has(value)) {
            this.items[value] = value;
            return true;
        }
        return false;
    }

    delete (value) { // 从集合中删除对应的元素
        if (this.has(value)) {
            delete this.items[value];
            return true;
        }
        return false;
    }

    has (value) { // 判断给定的元素在集合中是否存在
        return this.items.hasOwnProperty(value);
    }

    clear() { // 清空集合内容
        this.items = {};
    }

    size () { // 获取集合的长度
        return Object.keys(this.items).length;
    }

    values () { // 返回集合中全部元素的内容
        return Object.values(this.items);
    }
}

  在使用JavaScript对象{ }来表示集合时,咱们能够像操做数组同样经过[ ]来设置和获取集合内元素的值。经过这种方式,在设置集合元素的值时,若是元素不存在,则建立一个新元素,若是元素存在,则修改对应的值;在获取集合元素的值时,若是元素存在,则返回对应的值,若是元素不存在,则返回undefined。此外,JavaScript对象还提供了一些基础方法以方便咱们对集合进行一些操做,例如hasOwenProperty()方法返回一个代表对象是否具备特定属性的布尔值,Object.keys()方法返回指定对象的全部属性名称的数组,Object.values()方法方法指定对象的全部属性值的数组。this

  上述代码很简单,这里就再也不详细解释了。下面是一些测试用例和测试结果:spa

let set = new Set();
set.add(1);
console.log(set.values()); // [ 1 ]
console.log(set.has(1)); // true
console.log(set.size()); // 1

set.add(2);
console.log(set.values()); // [ 1, 2 ]
console.log(set.has(2)); // true
console.log(set.size()); // 2

set.delete(1);
console.log(set.values()); // [ 2 ]

set.delete(2);
console.log(set.values()); // []

  下面咱们来看看集合的数学运算:并集、交集、差集、子集。code

并集

  对于给定的两个集合,并集返回一个包含两个集合中全部元素的新集合。示意图以下:对象

  并集的实现代码:blog

union (otherSet) { // 并集
    let unionSet = new Set();
    this.values().forEach(value => unionSet.add(value));
    otherSet.values().forEach(value => unionSet.add(value));
    return unionSet;
}

  首先遍历第一个集合,将全部的元素添加到新集合中,而后再遍历第二个集合,将全部的元素添加到新集合中。而后返回新集合。不用担忧会添加剧复的元素,由于集合的add()方法会自动排除掉已添加的元素。ip

  测试用例及结果:get

let setA = new Set();
setA.add("first");
setA.add("second");
setA.add("third");

let setB = new Set();
setB.add("third");
setB.add("fourth");
setB.add("fifth");
setB.add("sixth");

console.log(setA.union(setB).values()); // [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth' ]

交集

  对于给定的两个集合,交集返回一个包含两个集合中共有元素的新集合。示意图以下:

  交集的实现代码:

intersection (otherSet) { // 交集
    let intersectionSet = new Set();
    this.values().forEach(value => {
       if (otherSet.has(value)) intersectionSet.add(value);
    });
    return intersectionSet;
}

  遍历第一个集合,若是元素出如今第二个集合中,则将它添加到新集合。而后返回新集合。

  测试用例及结果:

let setA = new Set();
setA.add("first");
setA.add("second");
setA.add("third");

let setB = new Set();
setB.add("second");
setB.add("third");
setB.add("fourth");

console.log(setA.intersection(setB).values()); // [ 'second', 'third' ]

差集

  对于给定的两个集合,差集返回一个包含全部存在于第一个集合且不存在于第二个集合的元素的新集合。示意图以下:

  差集的实现代码:

difference (otherSet) { // 差集
    let differenceSet = new Set();
    this.values().forEach(value => {
       if (!otherSet.has(value)) differenceSet.add(value);
    });
    return differenceSet;
}

  遍历第一个集合,若是元素没有出如今第二个集合中,则将它添加到新集合。而后返回新集合。

  测试用例及结果:

let setA = new Set();
setA.add("first");
setA.add("second");
setA.add("third");

let setB = new Set();
setB.add("second");
setB.add("third");
setB.add("fourth");

console.log(setA.difference(setB).values()); // [ 'first' ]

子集

  验证一个给定集合是不是另外一个集合的子集,即判断给定的集合中的全部元素是否都存在于另外一个集合中,若是是,则这个集合就是另外一个集合的子集,反之则不是。示意图以下:

  子集的实现代码:

subset (otherSet) { // 子集
    if (this.size() > otherSet.size()) return false;

    let isSubset = true;
    this.values().every(value => {
        if (!otherSet.has(value)) {
            isSubset = false;
            return false;
        }
        return true;
    });

    return isSubset;
}

  若是集合A比集合B的长度大,则直接返回false,由于这种状况A不多是B的子集。而后使用every()函数遍历集合A的全部元素,一旦碰到其中的元素没有在集合B中出现,则直接返回false,并终止遍历。这里咱们没有使用forEach来遍历集合A,是由于你没法根据某个条件来终止forEach循环。考虑下面这种状况:

var arr = ["first", "second", "third", "fourth"];
arr.forEach(item => {
    if(item === "third") return true;
    console.log(item);
});

  输出结果是:

first
second
fourth

  很显然,这里的return true语句并不能退出forEach循环,它只能保证本次循环中余下的语句不被执行,而接下来其它的元素仍是会被遍历到。

  在咱们的subset()方法中,若是使用forEach语句,每一次都会遍历集合中的全部元素,若是遇到其中的元素没有在集合B中出现,就将isSubset变量的值设置为false,但并不能退出forEach,isSubset变量的值可能会被屡次覆盖。为了提升执行效率,推荐使用every()函数,它会遍历集合中的元素,直到其中一个返回结果为false,就终止遍历,并返回false,不然就遍历全部的元素并返回true。有关every()函数的详细介绍能够看这里。与every()函数功能类似还有一个some()函数,它在遍历集合的过程当中,遇到返回结果为true时就终止遍历,具体内容能够看这里

  差集的测试用例及结果:

let setA = new Set();
setA.add("first");
setA.add("second");

let setB = new Set();
setB.add("first");
setB.add("second");
setB.add("third");

let setC = new Set();
setC.add("second");
setC.add("third");
setC.add("fourth");

console.log(setA.subset(setB)); // true
console.log(setA.subset(setC)); // false

   文章的开头说过,ES6提供了原生的Set类,让咱们来看看它的一些使用方法:

let set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set.values()); // [Set Iterator] { 1, 2, 3 }
console.log(set.has(1)); // true
console.log(set.size); // 2

set.delete(1);
console.log(set.values()); // [Set Iterator] { 2, 3 }

set.clear();
console.log(set.values()); // [Set Iterator] {  }

  和前面咱们自定义的Set类稍微有一点不一样,values()方法返回的不是一个数组,而是Iterator迭代器。另外一个就是这里的size是一个属性而不是方法,其它部分都和咱们前面定义的Set类相同。因为ES6的Set类不包含对集合的数学运算,咱们能够按照前面咱们提供的方法来对其进行扩充。有关ES6的Set类的详细介绍能够看查看这里

  下一章咱们将介绍如何用JavaScript来实现字典和散列表。

相关文章
相关标签/搜索