JavaScript 数据结构(4)-集合

126 阅读5分钟

学习代码 git 仓库地址:gitee.com/zhangning18…

七、集合

集合是一组无序且唯一的项组成。

在数学中,集合是一组不同对象的集。

比如:一个大于0的整数组成的自然数集合 N={1, 2, 3, 4, 5, ...}。集合中的对象列表用花括号 {} 包围。

还有一个概念叫空集。就是不包含任何元素的集合。用 {} 表示空集。

7.1 创建集合类

经常用到的集合方法大概如下:

  • add(element):向集合添加一个新元素
  • delete(element):从集合移除一个元素
  • has(element):如果元素在集合中,返回 true,否则返回 false
  • clear():移除集合中的所有元素
  • size():返回集合所包含元素的数量
  • values():返回一个包含集合中所有值的数组

set.js

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

这里有个细节就是通过对象而不是数组来表示集合。也可以通过数组来实现。这里用到 JavaScript 的对象不允许一个键指向两个不同的属性,保证了集合里的元素都是唯一的。

7.1.1 has(element) 方法

先实现 has(element) 方法,在别的方法中要用到,用于检测某个元素是否存在于集合中。

has(element) {
    // in 运算符来验证给定元素是否是 items 对象的属性
    // return element in this.items;
    // 更好的实现方式
    // Object 原型有 hasOwnProperty 方法。该方法返回一个表明对象是否具有特定属性的布尔值。
    // in 运算符则返回表示对象在原型链上是否有特定属性的布尔值
    return Object.prototype.hasOwnProperty.call(this.items, element);
    // 这里也可以使用 this.items.has
  }

7.1.2 add方法

先检测是否存在于集合中,如果不存在则添加 element 到集合中返回 true,存在则不添加返回 false。添加一个 element 的时候,把它同时作为键和值保存,这样有利于查找该元素。

  add(element) {
    // 先判断集合中是否存在该元素
    if (!this.has(element)) {
      this.items[element] = element;
      return true;
    }
    return false;
  }

7.1.3 delete 和 clear 方法

  // 删除元素
  delete(element) {
    if (this.has(element)) {
      // 删除集合元素,通过 delete 操作符,可以实现属性的删除操作。
      delete this.items[element];
      return true;
    }
    return false;
  }

  // 清空集合
  clear() {
    // 要重置 items 对象,只要把一个空对象重新赋值给它就可以了。也可以迭代集合进行删除,就是有点麻烦了
    this.items = {};
  }

7.1.4 size 方法

  // 实现 size 方法
  // 方式一:向 LinkedList 中一样添加一个变量 length ,在 add 或者 delete 的时候改变它。
  // 方式二:使用 JavaScript 中的 Object 类的一个内置方法。
  size() {
    // JavaScript 的 Object 类有一个 keys 方法,返回一个包含给定对象所有属性的数组。
    // 在这种情况下,可以使用这个数组的 length 属性来返回 items 对象的属性个数。
    return Object.keys(this.items).length;
  }

  // 方式三:手动提取 items 对象的每一个属性,记录属性的个数并返回这个数。
  size2() {
    let count = 0;
    // 迭代 items 对象的所有属性
    for (const itemsKey in this.items) {
      // 检查他们是否是对象自身的属性(避免重复计数)。
      if (this.items.hasOwnProperty(itemsKey)) {
        count++;
      }
    }
    return count;
  }

以上的方式三,不能简单地使用 for-in 语句迭代 items 对象的属性,并递增 count 变量的值,还需要使用 has 方法(验证 items 对象具有该属性),因为对象的原型包含了额外的属性。

7.1.5 values 方法

  // 要实现 values 方法,同样可以使用 Object 类内置的 values 方法
  values() {
    // 方式1: Object.values() 返回一个包含给定对象所有属性值的数组。是在 ES2017中添加进来的
    // return Object.values(this.items);

    // 方式2: 迭代 items 对象的所有属性,添加到数组中
    let values = [];
    for (const itemsKey in this.items) {
      if (this.items.has(itemsKey)) {
        values.push(itemsKey);
      }
    }
    return values;
  }

7.2 集合运算

虽然这些运算常用于 关系型数据库 等SQL语句中,这里还是学习了解下。

常用的集合运算:

    • 并集:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。
    • 交集:对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。
    • 差集:对于给定的两个集合,返回一个包含所有存在于第一个集合切不存在于第二个集合的元素的新集合
    • 子集:验证一个给定集合是否是另一个集合的子集

7.2.1 并集

实现 Set 类的方法 union

  // 实现集合的并集内容
  union(otherSet) {
    // 创建一个新的集合
    const unionSet = new Set();
    // 当前集合添加到新的集合中
    this.values().forEach(value => unionSet.add(value));
    // 第二个集合添加到新的集合中
    otherSet.values().forEach(value => unionSet.add(value));
    return unionSet;
  }

7.2.2 交集

  // 实现集合的交集
  intersection(otherSet) {
    // 创建一个新的集合存放公共集合元素
    const newSet = new Set();
    // 迭代当前集合
    this.values().forEach(i=>{
      // 如果当前集合元素存在于另一个集合中则添加到新的集合中
      if (otherSet.has(i)){
        newSet.add(i)
      }
    })
    return newSet;
  }
  // 实现集合的交集优化
  intersection2(otherSet) {
    const intersectionSet = new Set();
    const values = this.values();
    const otherValues = otherSet.values();
    let biggerSet = values;
    let smallerSet = otherValues;
    if (otherValues.length - values.length > 0) {
      biggerSet = otherValues;
      smallerSet = values;
    }
    // 先便利数组短的那个
    smallerSet.forEach(value => {
      if (biggerSet.includes(value)) {
        intersectionSet.add(value);
      }
    });
    return intersectionSet;
  }

7.2.3 差集

  // 实现差集方法
  difference(otherSet) {
    const differenceSet = new Set();
    this.values().forEach(val => {
      if (!otherSet.has(val)) {
        differenceSet.add(val);
      }
    });
    return differenceSet;
  }

这里不能向交集的方法一样去优化,因为 setA 与 setB 之间的差集 和 setB 与 setA 之间的差集不同。

7.2.4 子集

  // 实现是否是子集的方法
  isSubsetOf(otherSet) {
    // 不是子集
    if (this.size() > otherSet.size()) {
      return false;
    }
    let isSubset = true;
    // 判断是否所有的元素都存在于 otherSet 中
    this.values().every(value => {
      if (!otherSet.has(value)) {
        isSubset = false;
        return false;
      }
      return true;
    });
    return isSubset;
  }

7.3 ES 新增的 Set 类

通过 扩展运算符来计算并集、交集、差集是一种简便的方法。

// 1.进行并集运算
new Set(new Set([...setA, ...setB]))

// 2.进行交集运算
new Set(new Set([...setA.filter(x => setB.has(x))]))

// 差集运算
new Set([...setA].filter(x => setB.has(x)))

7.4 多重集或袋

上面学到集合数据结构不允许存在重复的元素。但在数学中有一个叫作多重集的概念,它允许我们向集合中插入之前已经添加过的元素。这里不做过多的学习