你们好,我是图图。上一篇文章聊了链表的数据结构,链表包括:单向链表、双向链表、循环链表和有序链表这几个常见的链表。而在操做的过程当中,也是比较复杂的。由于它具有有一个指向下一个节点的指针,所以在操做的过程当中要多加当心。那么这一章咱们就来聊聊集合。下面咱们废话很少说,一切尽在代码中。数组
集合是由一组无序而且不能存在重复的元素组成的。你能够把集合当成一个既没有重复元素,又没有顺序的数组。markdown
学过 ES6 的同窗都知道,加入了一种新的数据结构Set
。也是本章所说的集合。下面将基于 ES6 的Set
数据结构来实现一个属于本身的Set
类。数据结构
下面来声明一个Set
类。函数
class Set {
constructor() {
this.items = {};
}
}
复制代码
这里用一个对象items
来表示集合,用对象的主要缘由是,对象不容许用一个键指向两个不一样的属性。这样也能确保集合中的元素是惟一的,不过也能够用数组来表示。性能
下面是集合的方法。测试
add(ele)
:向集合中添加一个元素。delete(ele)
:从集合中删除一个元素。has(ele)
:查看集合中是否有该元素存在,若是存在返回true
,不然返回false
。clear()
:删除集合中的全部元素。size()
:返回集合中元素的数量。values()
:返回一个包含集合中全部元素的数组。首先来实现has
方法,由于它会被add
和delete
方法调用。优化
has(ele) {
return Object.prototype.hasOwnProperty.call(this.items, ele);
}
复制代码
这里使用了Object
原型上的hasOwnProperty
方法。该方法返回一个布尔值,查看对象上是否具备指定的属性。固然你也能够用in
操做符。这两个方法的区别在于hasOwnProperty
是检查实例中是否具备指定的属性。而in
操做符是不管指定的属性是在原型中仍是在实例中都会返回true
。ui
下面是添加元素的方法。this
add(ele) {
if (!this.has(ele)) {
this.items[ele] = ele;
return true;
}
return false;
}
复制代码
add
方法相对于比较简单。首先,检查要添加的元素是否存在于集合中,没有就添加并返回true
,有就直接返回false
。spa
添加元素时,最好用它自己来看成键和值来保存,这样有利于查找该元素。
delete(ele) {
if (this.has(ele)) {
delete this.items[ele];
return true;
}
return false;
}
复制代码
delete
方法首先检查元素是否存在于集合中,存在就删除并返回true
表示已删除,不存在直接返回false
。
clear
方法和其余数据结构的clear
方法同样。
clear() {
this.items = {};
}
复制代码
直接将items
初始化就能够了。还有一种方法是经过迭代集合,使用delete
操做符逐个将元素删除,不过这样显得麻烦。
实现size
方法有三种:
add
和delete
方法是用一个count
变量控制它。Object.keys
方法,该方法返回对象中可枚举属性组成的数组。而后用这个数组的长度length
返回items
对象中的属性个数。for-in
遍历对象中的属性,用一个count
变量记录属性的个数并返回count
变量。size() {
return Object.keys(this.items).length;
}
theOtherOneSize() {
let count = 0;
for (const key in this.items) {
if (this.has(key)) {
count++;
}
}
return count;
}
复制代码
迭代items
对象的全部属性,并用has
方法检查是否为自身的属性。若是是,就递增count
变量。
用
has
方法这步的缘由在于,假设在对象的原型上有额外的属性,也会致使count
递增。
对于values
方法,用Object.values
和for-in
迭代均可以。
values() {
return Object.values(this.items);
}
theOtherOneValues() {
const arr = [];
for (const key in this.items) {
if (this.has(key)) {
arr.push(key);
}
}
return arr;
}
复制代码
用for-in
迭代就和上面的size
方法同样,只不过这里不是计算属性的个数。
const set = new Set();
console.log(set.size()); // 0
console.log(set.add(1)); // true
console.log(set.add(2)); // true
console.log(set.add(3)); // true
console.log(set.size()); // 3
console.log(set.has(1)); // true
console.log(set.values()); // [1, 2, 3]
console.log(set.delete(3)); // true
复制代码
class Set {
constructor() {
this.items = {};
}
has(ele) {
return Object.prototype.hasOwnProperty.call(this.items, ele);
}
add(ele) {
if (!this.has(ele)) {
this.items[ele] = ele;
return true;
}
return false;
}
delete(ele) {
if (this.has(ele)) {
delete this.items[ele];
return true;
}
return false;
}
clear() {
this.items = {};
}
size() {
return Object.keys(this.items).length;
}
theOtherOneSize() {
let count = 0;
for (const key in this.items) {
if (this.has(key)) {
count++;
}
}
return count;
}
values() {
return Object.values(this.items);
}
theOtherOneValues() {
const arr = [];
for (const key in this.items) {
if (this.has(key)) {
arr.push(key);
}
}
return arr;
}
}
复制代码
给定两个集合,返回一个包含两个集合中全部的元素的集合。
// 在Set类中添加方法
union(otherSet) {
const unionSet = new Set();
this.values().forEach((item) => unionSet.add(item));
otherSet.values().forEach((item) => unionSet.add(item));
return unionSet;
}
复制代码
const setA = new Set();
const setB = new Set();
setA.add(1);
setA.add(3);
setA.add(5);
setA.add(6);
setB.add(2);
setB.add(4);
setB.add(6);
const otherSetB = setA.union(setB);
console.log(otherSetB.values());
// [1, 2, 3, 4, 5, 6]
复制代码
须要注意的是,在setA
和setB
中都添加了元素6
,可是在下面打印出来的数据中只出现一个6
。
给定的两个集合,返回一个包含两个集合中共有元素的集合。
intersectionFn(otherSet) {
const intersection = new Set();
const values = this.values();
for (let i = 0; i < values.length; i++) {
if (otherSet.has(values[i])) {
intersection.add(values[i]);
}
}
return intersection.values();
}
复制代码
首先建立一个Set
实例,而后迭代当前Set
实例中的全部值(values
)。用has
方法验证是否存在otherSet
集合当中,若是存在,就添加到interseciton
集合中。最后以数组的形式返回它。
const setA = new Set();
const setB = new Set();
setA.add(1);
setA.add(3);
setA.add(5);
setA.add(6);
setB.add(2);
setB.add(4);
setB.add(6);
const otherSetB = setA.intersectionFn(setB);
console.log(otherSetB);
// [6]
复制代码
假设如今有两个这样的集合:
setA
的值是[1, 2, 3, 4, 5, 6]
;setB
的值是[2, 6]
;用刚才的intersectionFn
方法,就要迭代六次setA
的值,而后还要用这个六个值和setB
的两个值去做比较。那么换过来,只要用setB
去和setA
作比较的话,这样就减小了性能的消耗。由于只须要迭代两次setB
。下面来修改以前的intersectionFn
方法。
otherIntersection(otherSet) {
const intersection = new Set();
const values = this.values();
const otherVal = otherSet.values();
// 数组长度大的集合
let biggerSet = values;
// 数组长度小的集合
let smallerSet = otherVal;
/* 若是传入的Set集合的元素个数比当前Set集合的元素个数多,那么就交换 若是当前的集合元素个数比传入的Set集合的元素个数多,就不会走这一步 */
if (otherVal.length - values.length > 0) {
biggerSet = otherVal;
smallerSet = values;
}
// 迭代较小的集合
smallerSet.forEach((item) => {
if (biggerSet.includes(item)) {
intersection.add(item);
}
});
return intersection;
}
复制代码
给定两个集合,返回一个包含全部存在于第一个集合中但不存在于第二个集合中的元素的集合。
difference(otherSet) {
const differenceSet = new Set();
this.values().forEach((item) => {
if (!otherSet.has(item)) {
differenceSet.add(item);
}
});
return differenceSet.values();
}
复制代码
difference
方法会返回一个存在于集合setA
中但不存在于集合setB
的元素数组。首先建立结果集合,而后迭代集合中的全部值。检查当前值是否存在于给定集合中,存在的话,就把值添加到结果集合中。
const setA = new Set();
const setB = new Set();
setA.add(1);
setA.add(3);
setA.add(5);
setA.add(6);
setB.add(2);
setB.add(4);
setB.add(6);
const otherSetB = setA.difference(setB);
console.log(otherSetB);
// [1, 3, 5]
复制代码
这里输出了[1, 3, 5]
,由于[1, 3, 5]
只存在于setA
中。若是执行setB.difference(setA)
,会输出[2, 4]
,由于[2, 4]
只存在于setB
中。
这里不能像优化
intersecton
方法那样去优化difference
方法,由于setA
和setB
之间的差集可能与setB
和setA
之间的差集不同。
验证一个集合是不是另外一个集合的子集。
isSubsetOf(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;
}
复制代码
首先验证当前Set
实例的元素个数大小,若是当前实例中的元素比otherSet
实例多,那就不是子集。直接返回false
。记住,子集的元素个数始终小于或等于要比较的集合的。
上面的代码中,假如当前实例是给定集合的子集(isSubset = true
)。就迭代当前集合中的全部元素,验证这些元素是否存在于otherSet
中。若是都不存在,就说明它不是一个子集,返回false
。若是都存在于otherSet
中,那么isSubset
的值就不会改变。返回true
。
使用every
方法的缘由在于,一个值不存在于otherSet
中时,能够中止迭代,表示这不是一个子集。只要回调函数返回true
,就会执行下去。若是返回false
,循环直接中止。
const setA = new Set();
const setB = new Set();
const setC = new Set();
setA.add(1);
setA.add(2);
setA.add(3);
setB.add(1);
setB.add(2);
setB.add(3);
setB.add(4);
setC.add(2);
setC.add(3);
setC.add(4);
setC.add(5);
console.log(setA.isSubsetOf(setB)); // true
console.log(setA.isSubsetOf(setC)); // false
复制代码
这里有三个集合:setA
是setB
的子集,因此输出了true
。setA
不是setC
的子集,由于setC
中只包含了setA
中的2
,因此输出了false
。
下面咱们用 ES6 的 Set
类来模拟并集、交集、差集、子集。
首先来定义两个集合,由于后面会用到。
const setA = new Set();
const setB = new Set();
setA.add(10);
setA.add(20);
setA.add(30);
setB.add(20);
setB.add(30);
setB.add(40);
setB.add(50);
复制代码
const union = (setA, setB) => {
let values = new Set();
setA.forEach((item) => values.add(item));
setB.forEach((item) => values.add(item));
return values.values();
};
console.log(union(setA, setB));
// {10, 20, 30, 40, 50}
复制代码
const intersection = (setA, setB) => {
let values = new Set();
setA.forEach((item) => {
if (setB.has(item)) {
values.add(item);
}
});
return values.values();
};
console.log(intersection(setA, setB));
// {20, 30}
复制代码
const difference = (setA, setB) => {
const values = new Set();
setA.forEach((item) => {
if (!setB.has(item)) {
values.add(item);
}
});
return values.values();
};
console.log(difference(setA, setB));
// {10}
复制代码
const isSubsetOf = (setA, setB) => {
if (setA.size > setB.size) {
return false;
}
let isSubset = true;
let arr = [...setA];
arr.every((value) => {
if (!setB.has(value)) {
isSubset = false;
return false;
}
return true;
});
return isSubset;
};
console.log(isSubsetOf(setA, setB)); // false
复制代码
若是文中出现错误,欢迎各位大佬指点。若是本章内容对你有收获,欢迎点赞加关注哦!