什么是集合
集合
集合是由一组无序且惟一 (即不能重复) 的项组成。算法
在数学中,集合是一组不一样对象的集合。好比是说,一个又大于或等于 0 的整数组成的天然数集合: N = {0, 1, 2, 3,4,5, 6, ...}。集合中的对象列表用花括号 {} 包围。数组
空集
空集是不包含任何元素的集合。空集用 {} 表示。浏览器
实现集合
定义集合类
咱们使用 ES6 的 class 语法来建立一个基于对象的 Set 类:数据结构
class Set {
constructor() {
this.items = {}
}
}
复制代码
接下来,咱们为集合声明一些可用的方法:this
- add(element) 向集合添加一个新元素
- delete(element) 从集合中移除一个元素
- has(element) 判断一个元素是否在集合中
- clear() 移除集合中的全部元素
- size() 返回集合中所包含元素的数量
- values() 返回一个包含集合中全部元素的数组
- isEmpty() 判断集合是否为空
- size() 返回集合中元素个数
- clear() 清空集合
- toString() 将集合中的元素以字符串形式输出
下面,咱们逐一实现这些方法:spa
add 向集合添加一个新元素
add(element) {
if (!this.has(element)) {
this.items[element] = element;
return true;
}
return false;
}
复制代码
向集合中添加一个 element 的时候,首先检查它是否存在于集合中,若是不存在,就将 element 添加到集合中,并返回 true,表示添加了该元素;若是集合中已经有了这个element,则返回 false ,表示没有添加这个元素。prototype
delete 从集合中移除一个元素
delete(element) {
if (this.has(element)) {
delete this.items[element];
return true;
}
return false;
}
复制代码
一样的,从集合中移除一个元素时,首先判断该元素是否存在于集合中,若是存在,就从集合中移除,并返回 true,表示元素被移除;若是不存在,则返回 false。3d
has 判断一个元素是否在集合中
has(element) {
return Object.prototype.hasOwnProperty.call(this.items, element);
}
复制代码
咱们使用 Object 的原型方法 hasOwnProperty 来判断元素是否在集合中。hasOwnProperty 方法返回一个代表对象是否具备特定属性布尔值。code
clear 移除集合中的全部元素
clear() {
this.items = {}
}
复制代码
size 返回集合中所包含元素的数量
方法一:使用 Object.keys()对象
size() {
return Object.keys(this.items).length;
}
复制代码
在 size 方法中,咱们使用 Object 类的 keys 方法来获取集合中所包含元素的数量。keys 方法返回一个包含给定对象全部属性的数组,而后咱们能够使用数组的 length 属性来返回集合中的元素数量。
方法二:使用 for...in 循环
size() {
let count = 0;
for (let key in this.items) {
if (this.items.hasOwnProperty(key)) {
count++
}
}
return count;
}
复制代码
咱们定义一个 count 变量来存储集合中元素的个数, 而后使用 for...in 循环迭代 items 对象的全部属性,检查它们是不是对象自身的属性,若是是,就递增 count 变量的值,最后在方法结束时返回 count 变量。
values 返回一个包含集合中全部元素的数组
方法一:使用 Object.values()
values() {
return Object.values(this.items);
}
复制代码
咱们使用 Object 类内置的 values 方法能够很轻松的获取集合中的全部元素,但Object.values 方法目前只在现代浏览器中可用。
方法二:使用 for...in 循环
values() {
let values = [];
for (let key in this.items) {
if (this.items.hasOwnProperty(key)) {
// 注意,咱们保存的集合元素 key 与 value 相同,所以能够直接将 key push 进 values 中
values.push(key)
}
}
}
复制代码
isEmpty 判断集合是否为空
isEmpty() {
return this.size() === 0;
}
复制代码
toString 将集合中的元素以字符串形式输出
toString() {
if (this.isEmpty()) {
return '';
}
const values = this.values();
let objString = `${values[0]}`;
for (let i = 1; i < values.length; i++) {
objString = `${objString}, ${values[i].toString()}`;
}
return objString;
}
复制代码
集合运算
在数学中,集合有 并集、交集、差集、子集等运算,咱们也能够使用咱们定义的集合类进行这些运算。
- 并集:对于给定的两个集合,返回一个包含两个集合中全部元素的新集合
- 交集:对于给定的两个集合,返回一个包含两个集合中共有元素的集合
- 差集:对于给定的两个集合,返回一个包含全部存在于第一个集合且不存在于第二个集合的元素的新集合
- 子集:验证一个给定集合是不是另外一个集合的子集
并集
在数学概念中,集合 A 和集合 B 的并集表示以下:
A ∪ B
该集合的定义以下:
A ∪ B = {x | x ∈ A ∨ x ∈ B}
意思是 x (元素) 存在于 A 中,或 x 存在于 B 中。下图展现了并集运算:
下面,咱们实现 Set 类的 union 方法:
union(otherSet) {
// 存放并集的结果
const unionSet = new Set();
// 迭代当前集合的全部值,将值添加到并集 集合中
this.values().forEach(value => unionSet.add(value));
// 迭代 otherSet 的全部值,将值添加到并集 集合中
otherSet.values().forEach(value => unionSet.add(value));
return unionSet;
}
复制代码
首先使用 ES6 提供的数据结构 Set 来建立一个新的集合 unionSet,表明两个集合的并集。接着获取第一个集合(当前的 Set 类实例)的全部值,迭代并所有添加到表明并集的集合中。而后对第二个集合作一样的是,最后将表明并集的集合返回。
交集
在数学概念中,集合 A 和集合 B 的交集表示以下:
A ∩ B
该集合的定义以下:
A ∩ B = {x | x ∈ A ∧ x ∈ B}
意思是 x (元素) 存在于 A 中,且 x 存在于 B 中,下图展现了交集运算:
下面,咱们实现 Set 类的 intersection 方法:
intersection(otherSet) {
// 存放交集的结果
const intersectionSet = new Set();
// 当前集合(当前 Set 类的实例)的全部值
const values = this.values();
// 第二个集合的全部值
const otherValues = otherSet.values();
// 假设当前的集合元素较多
let biggerSet = values;
// 假设第二个集合元素较少
let smallerSet = otherValues;
// 比较两个集合的元素个数,若是另一个集合的元素多于当前集合,则交换 biggerSet 和 smallerSet
if (otherValues.length - values.length > 0) {
biggerSet = otherValues;
smallerSet = values;
}
// 迭代较小的集合,计算出两个集合共有元素添加到 交集 集合中
smallerSet.forEach(value => {
if (biggerSet.includes(value)) {
intersectionSet.add(value);
}
})
// 将交集结果返回
return intersectionSet;
}
复制代码
intersection 方法会获得全部同时存在于两个集合中的元素。咱们首先建立一个新的集合 intersectionSet 来存放交集的结果。而后分别获取当前 Set 实例中的全部元素和另外一个集合中的全部元素。咱们假定当前集合的元素较多,另外一个集合的元素较少,比较两个集合的元素个数,若是另外一个集合的元素个数多于当前集合中的元素个数,咱们就交换 biggerSet 和 smallerSet 的值。最后迭代较小集合 smallerSet 来计算出两个集合的共有元素。
差集
在数学概念中,集合 A 和集合 B 的差集表示为:
A﹣B
该集合的定义以下:
A﹣B = {x | x ∈ A ∧ x ∉ B}
意思是 x (元素) 存在于 A 中,且 x 不存在于 B 中。下图展现了集合 A 和集合 B 的差集运算:
下面,咱们来实现 Set 类的 subtract 方法:
subtract(otherSet) {
// 存放差集结果
const subtractionSet = new Set();
// 迭代当前集合的全部值
this.values().forEach(value => {
// 当前值不存在于 otherSet 中,将其添加到 差集 集合中
if (!otherSet.has(value)) {
// 将元素添加到集合中
subtractionSet.add(value);
}
})
return subtractionSet;
}
复制代码
subtract 方法会获得全部存在于集合 A 但不存在于集合 B 的元素。咱们首先建立一个新的集合变量 subtractionSet 来存放差集结果。经过 this.values() 拿到集合A 中的全部值,而后迭代集合 A 中的全部值,将存在于集合A 但不存在于集合B 中的元素放入 subtractionSet 集合中,迭代完集合 A 中的全部元素后,subtractionSet 集合中的元素就是集合A 与集合B 差集。
子集
在数学概念中,集合 A 是集合 B 的一个子集(或者 B 包含 A),表示以下:
A ⊆ B
该集合的定义以下:
{x|∀x∈A ⇒ x ∈ B}
意思是集合A 中的每个 x (元素),也须要存在于集合B 中。下图展现了集合A 是集合B 的子集:
下面,咱们来实现 Set 类的 isSubsetOf 方法:
isSubsetOf(otherSet) {
// 验证当前 Set 实例的大小
// 当前 Set 实例的元素多于 otherSet,那么当前 Set 实例不是 otherSet 的子集
if (this.size() > otherSet.size()) {
return false;
}
// 假定当前 Set 实例是给定集合的子集
let isSubset = true;
// 迭代当前 Set 实例的全部元素
this.values().every(value => {
// 有任何元素不存在于 otherSet 中,那么当前 Set 实例就不是 otherSet 的子集
if (!otherSet.has(value)) {
isSubset = false;
return false;
}
return true;
})
return isSubset;
}
复制代码
isSubsetOf 方法验证 集合 A 是不是 集合 B 的子集。咱们首先须要验证当前 Set 实例的大小(集合A),若是当前实例中的元素比 otherSet 实例(集合B)更多,说明当前 Set 实例不是 otherSet 的子集。(子集的元素个数须要小于等于要比较的集合)。
接下来,咱们假定当前集合是给定集合的子集,而后迭代当前集合的全部元素,并验证这些元素是否存在于 otherSet 中。若是有任何一个元素不存在于 otherSet 中,那么当前集合就不是给定集合的子集,咱们就返回 false。若是全部元素都存在于 otherSet 中,那么当前集合就是给定集合的子集。
完整代码
class Set {
constructor() {
this.items = {};
}
// 向集合添加一个新元素
add(element) {
if (!this.has(element)) {
this.items[element] = element;
return true;
}
return false;
}
// 从集合中移除一个元素
delete(element) {
if (this.has(element)) {
delete this.items[element];
return true;
}
return false;
}
// 判断一个元素是否存在于集合中
has(element) {
return Object.prototype.hasOwnProperty.call(this.items, element);
}
// 移除集合中的全部元素
clear() {
this.items = {};
}
// 返回集合中所包含元素的数量
size() {
let count = 0;
for(let key in this.items) {
if (this.items.hasOwnProperty(key)) {
count++;
}
}
return count;
}
// 返回一个包含集合中全部元素的数组
values() {
let values = [];
for (let key in this.items) {
if (this.items.hasOwnProperty(key)) {
values.push(key);
}
}
return values;
}
// 并集
union(otherSet) {
const unionSet = new Set();
this.values().forEach(value => unionSet.add(value));
otherSet.values().forEach(value => unionSet.add(value));
return unionSet;
}
// 交集
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(value => {
if (biggerSet.includes(value)) {
intersectionSet.add(value);
}
})
return intersectionSet;
}
// 差集
subtract(otherSet) {
const subtractionSet = new Set();
this.values().forEach(value => {
if (!otherSet.has(value)) {
subtractionSet.add(value)
}
})
return subtractionSet;
}
// 子集
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;
}
}
复制代码
ES6 Set 类的集合运算
并集
// A 和 B 的并集
function union(setA, setB) {
const unionSet = new Set([...setA, ...setB]);
return unionSet;
}
复制代码
交集
// A 和 B 的交集
function intersection(setA, setB) {
const intersect = new Set([...a].filter(x => b.has(x)));
return intersect
}
复制代码
差集
// A 相对于 B 的差集
function subTract(setA, setB) {
const subTractSet = new Set([...a].filter(x => !b.has(x)));
return subTractSet;
}
复制代码
参考资料:
书籍:《JavaScript数据结构与算法》