集合是由一组无序且唯一的项组成的,该数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。
ECMAScript 2015介绍了Set类是JavaScript API的一部分。本章节将基于Set类来实现自己的Set类。
class Set {
constructor() {
this.items = {}
}
}
下面,来看一下一些集合可用的方法
add(elemet): 向集合添加一个新元素
delete(element): 从集合移除一个元素
has(element): 判断一个元素是否在集合中
clear(): 清空集合
size(): 返回集合所包含元素的数量
valuse(): 返回一个包含集合中所有值的数组
has(element) {
return Object.prototype.hasOwnProperty.call(this.items, element);;
}
Object原型有hasOwnProperty方法,该方法返回一个表明对象是否具有特定属性的布尔值。in运算符则返回标识对象在原型链上是否有特定属性的布尔值
add(element) {
if(!this.has(element)) {
this.items[element] = element;
return true;
}
return false;
}
对于给定的element,可以检查它是否存在于集合中,如果不存在,就把element添加到集合中
delete(element) {
if(this.has(element)) {
delete this.items[element];
return true;
}
return false;
}
clear() {
this.items = {}
}
在delete方法中,需要验证给定的element是否存在于集合中,如果存在,则移除 要重置items对象,直接将一个空对象重新赋值给它即可,也可以迭代集合,用delete方法依次移除所有的值
size() {
return Object.keys(this.items).length;
}
sizeLegacy() {
let count = 0;
for(let key in this.items) {
if(this.items.hasOwnProperty(key)) {
count ++;
}
}
return count;
}
该方法有三种实现方式 第一种方式是使用一个length变量,每当使用add或delete方法时就控制它 第二种是使用JavaScript中Object类的一个内置方法(ECMAScript 2015以上版本) Javascript的Object类有一个keys方法,返回一个包含给定对象所有属性的数组 第三种方法是手动提取items对象的每一个属性,记录属性的个数并返回这个数 迭代items对象的所有属性,检查它是否是对象自身的属性(避免重复计数)
注意:不能简单的使用for-in语句迭代items对象的属性,并递增count变量的值,还需要使用has方法,以验证items对象具有该属性,因为对象的原型包含了额外属性(属性既有继承自JavsScript的Object类的,也有属于对象自身、未用于数据结构的)
values() {
return Object.values(this.items);
}
valuseLegacy() {
let values = [];
for(let key in this.items) {
if(Object.prototype.hasOwnProperty.call(this.items, key)){
values.push(key);
}
}
return values;
}
要实现values方法,同样可以使用Object类内置的values方法
集合在计算机科学中主要应用之一是数据库,集合被用于查询和设计的处理。当创建一条SQL查询命令时,可以只当是从表中获取全部数据还是获取其中的子集,也可以获取两张表中共有的数据、之村子啊与一张表中的数据,或是存在于两张表内的数据。这些SQL领域的运算叫联接,而SQL联接的基础就是集合运算。
集合相关运算
并集:对于给定的集合,返回一个包含两个集合中所有元素的新集合。
交集:对于给的那个的两个集合,返回一个包含两个集合中共有元素的新集合。
差集:对于给定的两个集合,返回一个包含所有窜在于第一个集合且不存在与第二个集合的元素的新集合。
子集:验证一个给定集合是否是另一个集合的子集
union(otherSet) {
const unionSet = new Set();
this.values().forEach(ele => unionSet.add(ele));
otherSet.values().forEach(ele => unionSet.add(ele));
return unionSet;
}
首先需要创建一个新的集合,代表两个集合的并集,接下来获取第一个集合所有的值并添加到新的集合中,同时将第二个集合的值也添加到新的集合中并返回新的集合。
intersection(otherSet) {
const intersectionSet = new Set();
const values = this.values;
for(let i = 0; i < values.length; i++) {
if(otherSet.has(values[i])) {
intersectionSet.add(values[i]);
}
}
return intersectionSet;
}
需要找到当前Set实例中所有也存在于给定Set实例中的元素,迭代当前Set实例的所有制,验证它们是否也存在于ohterSet实例中,如果存在,则添加到新的Set中。
优化intersection方法,如果集合1的元素比集合2的元素多,则需要迭代多次集合1的值
intersection(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(ele => {
if(biggerSet.includes(values)) {
intersectionSet.add(values);
}
})
return intersectionSet;
}
首先创建一个新的集合来存放intersection方法返回的结果,同样要获取当前集合实例中的值和作为参数传入intersection方法的值,比较两个集合哪个元素的值更少,迭代元素数量少的集合来计算两个集合的共有元素并返回
difference(otherSet) {
const differenceSet = new Set();
this.values().forEach(ele => {
if(!otherSet.has(ele)) {
differenceSet.add(ele);
}
})
return differenceSet;
}
实现方式类似于intersection,不同的是,不能同优化intersection一个来优化此方法,因为集合1与集合2的差集可能与集合2与集合1的差集不一样
inSubsetOf(otherSet) {
if(this.size() > otherSet.size()) {
return false;
}
let isSubset = true;
this.values().every(ele => {
if(!otherSet.has(ele)) {
isSubset = false;
return false;
};
})
return isSubset;
}
第一步验证当前Set集合的大小,如果当前集合比otherSet实例更多,就不是一个子集。 接下来遍历当前集合,如果当前集合中存在某个元素,但是otherSet中不存在,则当前元素不是子集,反之,如果当前集中的所有元素,otherSet中都存在,则为子集
不同于上述方法创建的Set,ES2015的Set的values方法会犯Iterator,而不是值构成的数组,另一个区别是上述方法创建的Set的size是一个方法,而原生的Set是size属性
const union = (setA, setB) => {
const unionSet = new Set();
setA.forEach(ele => unionSet.add(ele));
setB.forEach(ele => unionSet.add(ele));
return unionSet;
}
const intersetcion = (setA, setB) => {
const intersectionSet = new Set();
setA.forEach(ele => {
if(setB.has(ele)) {
intersectionSet.add(ele);
}
})
return intersectionSet;
}
const difference = (setA, setB) => {
const differenceSet = new Set();
setA.forEach(ele => {
if(!setB.has(ele)) {
differenceSet.add(ele);
}
})
return differenceSet;
}
使用扩展运算符计算并集、交集、差集的方法包含三个步骤 1.将集合转化为数组 2.执行需要的运算 3.将结果转化回集合
// 并集
const unionSet = new Set([...setA, ...setB]);
// 交集
const intersetcionSet = new Set([...setA].filter(ele => setB.has(ele)));
// 差集
const differenceSet = new Set([...setA].filter(ele => !setB.has(ele)));