前面已经学习了数组(列表)、栈、队列和链表等顺序数据结构。这一章,我们要学习集合,这是一种不允许值重复的顺序数据结构。

本章可以学习到,如何添加和移除值,如何搜索值是否存在,也可以学习如何进行并集、交集、差集等数学操作,还可以学到如何使用ES6 原生的 Set类

第六章 集合

构建数据集合

集合是由一组无序且唯一(即不重复)的项组成的。这个数据结构使用了与有限集合相同的属性概念,但应用在计算机科学的数据结构中。

在数学中,集合时一组不同的对象(的集)。

比如说,一个由大于或等于0 的整数组成的自然数集合: N:{0,1,2,3,4,5,6,...}。在集合中的对象列表用 {}(大括号)包围

还有一个概念叫做空集。空集就是不包含任何元素的集合。

可以把集合想象成一个既没有重复元素,也没有顺序概念的数组。

在数学中,集合也有并集、交集、差集等基本操作。

创建集合

下面Set类的骨架

function Set(){
let items = {};
}

我们使用对象而不是数组来表示集合(items),但也可以用数组实现。这里我们用对象来实现,同时,JavaScript 对象不允许一个键指向两个不同的属性,也保证了集合里的元素都是唯一的。

接下来,需要声明一些结合可用的方法

  • add(value):向集合添加一个新的项
  • remove(value):从集合移除一个值
  • has(value):如果值在集合中,返回 true,否则返回 false
  • clear():移除集合中的所有项
  • size():返回集合所包含元素的值,与数组的 length 属性类似
  • values():返回一个包含集合中所有值的数组

has(value)方法

首先要实现的就是 has(value)方法,这是因为它会被 add、remove 等方法调用。

this.has = function(value){
return value in items;
}

既然我们使用对象来存储集合的值,就可以使用 JavaScript 的 in 操作符来验证给定的值是否是 items 对象的属性。

但这个方法还有一个更好的实现方式

this.has = function(value){
return items.hasOwnProperty(value);
}

所有的 JavaScript 对象都有 hasOwnProperty方法,这个方法返回一个表明对象是否具有特定属性的布尔值。

inhasOwnProperty 的区别

in判断的是对象的所有属性,包括对象实例及其原型的属性。而hasOwnProperty则是判断对象实例的是否具有某个属性。

这也是为什么 for in 循环的时候会建议使用 hasOwnProperty 进行过滤,在使用了 eslint 才不会报警告,同时也是为了可以有效避免扩展本地原型而引起的错误。

for (var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}

add 方法

this.add = function(value){
if(!this.has(value)){
items[value] = value
return true
}
return false;
}

对于给定的 value,可以检测是否存在于集合中。如果不存在,就把 value 添加到结合中,返回 true,表示添加了这个值。如果集合中,已经有了,就返回 false,表示没有添加它。

remove 和 clear 方法

remove 方法

this.remove = function(value){
if(this.has(value)){
delete items[value];
return true;
}
return false;
}

因为使用对象来存储集合的 items 对象,可以简单使用 delete 操作符从 items 对象中移除属性

clear 方法

this.clear = function(){
items = {};
}

重置 items 对象,需要做的只是把一个空对象重新赋值给它,我们也可以迭代集合,用 remove 方法依次移除所有的值,但既然有更简单的方法,那样做就太麻烦了。

size 方法

返回集合中有多少项,有三种实现方法

第一种是像之前一样,使用 length 变量,每当移除或者添加的时候控制它。

第二种是使用 JavsScript 内建的 Object 类的一个内建函数

this.size = function(){
return Object.keys(items).length
}

JavaScript 的 Object 类有一个 keys 方法,它返回一个包含给定对象所有属性的数组。在这种情况下,可以使用这个数组的 length 属性来返回 items 对象的属性个数。

第三种方法是手动提取 items 对象的每一个属性,记录属性的个数并返回这个数字。

this.size = function(){
let count = 0;
for(let key in items){
if(items.hasOwnProperty(key)){
++count;
}
}
return count;
}

遍历 items 对象的所有属性,检查它们是否是对象自身的属性(避免重复计数),如果是,就递增 count 变量的值,最后在方法结束时返回这个数字。

values 方法

values 方法也应用了相同的逻辑,提取 items 对象的所有属性,以数组的形式返回

this.values = function(){
let values = [];
for(let i=0 , keys=Object.keys(items); i<keys.length; i++){
values.push(items[keys[i]]);
}
return values;
}

还有兼容旧浏览器的写法

this.values = function(){
let values = [];
for(let key in items){
if(items.hasOwnProperty(key)){
values.push(items[key])
}
}
return values;
}

Set类全部代码

function Set(){
let items = {};
this.has = function(value){
return items.hasOwnProperty(value);
}
this.add = function(value){
if(!this.has(value)){
items[value] = value
return true
}
return false;
}
this.remove = function(value){
if(this.has(value)){
delete items[value];
return true;
}
return false;
}
this.clear = function(){
items = {};
} this.size = function(){
let count = 0;
console.log(items);
for(let key in items){
if(items.hasOwnProperty(key)){
++count;
}
}
return count;
}
this.values = function(){
let values = [];
for(let i=0 , keys=Object.keys(items); i<keys.length; i++){
values.push(items[keys[i]]);
}
return values;
}
}

使用 Set类

现在数据结构已经完成了 。测试一下

let set = new Set(); // 新建 Set类 实例
set.add(1);
set.add(2);
set.add(3);
set.add('j');
console.log(set.has(2)); // true
console.log(set.size()); // 4
console.log(set.value()); // [1, 2, 3, "j"]
set.remove(2);
console.log(set.has(2)); // false
console.log(set.size()); // 3
console.log(set.value()); //  [1, 3, "j"]
set.clear();
console.log(set.size()); // 0
console.log(set.value()); // []

集合操作

对集合可以进行以下操作

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

并集

// 并集
this.union = function(otherSet){
let unionSet = new Set();
let values = this.values();
for(let i = 0; i < values.length; i++){
unionSet.add(values[i]);
}
values = otherSet.values();
for(let i = 0; i < values.length; i++){
unionSet.add(values[i]);
}
return unionSet;
}

首先需要创建一个新的集合,代表两个集合的并集,获取第一个集合(当前Set类实例)所有值(values),遍历并添加到代表并集的集合中。然后对第二个集合做同样的事情,最后返回结果。

测试上面的代码

let setA = new Set();
setA.add(1);
setA.add(2);
setA.add(3);
let setB = new Set();
setB.add(3);
setB.add(4);
setB.add(5);
setB.add(6);
console.log(setA.values()); // [1, 2, 3]
console.log(setB.values()); // [3, 4, 5, 6]
let unionAB = setA.union(setB);
console.log(unionAB.values()); // [1, 2, 3, 4, 5, 6]

可以注意到元素 3同时存在于A和B中,但是集合里只输出了一次

交集

// 交集
this.intersection = function(otherSet){
let intersectionSet = new Set();
let values = this.values();
for(let i = 0;i <values.length; i++){
if(otherSet.has(values[i])){
intersectionSet.add(values[i]);
}
}
return intersectionSet;
}

验证

let intersectionSetAB = setA.intersection(setB);
console.log(intersectionSetAB.values()); // [3]

差集

// 差集
this.difference = function(otherSet){
let differenceSet = new Set();
let values = this.values();
for(let i = 0; i < values.length; i++){
if(!otherSet.has(values[i])){
differenceSet.add(values[i]);
}
}
return differenceSet;
}

验证

let differenceSetAB = setA.difference(setB);
console.log(differenceSetAB.values()); // [1, 2]

differenceSetAB 会得到所有存在于集合A 但不存在与集合B的值。

子集

// 子集
this.subset = function(otherSet){
if(this.size() > otherSet.size()){
return false
}else{
let values = this.values();
for(let i = 0; i < values.length; i++){
if(!otherSet.has(values[i])){
return false;
}
}
return true;
}
}

首要要验证当前Set实例的大小,如果当前实例中的元素比 otherSet 实例更多,它就不是一个子集。子集的元素个数需要小于或等于要比较的集合。

接下来遍历所有集合中的元素,验证这些元素也存在于 otherSet 中,如果有任何元素不存在与 otherSet 中的,这意味着它不是一个子集,返回 false。如果所有元素都存在于 otherSet 中,就返回 true。

验证

let subSetAB = setA.subset(setB);
console.log(subSetAB); // false setB.add(1);
setB.add(2);
setB.add(3);
console.log(subSetAB); // true

ES6——Set类

ES6新增了 Set 类,我们可以基于 ES6 的 Set 开发我们的 Set 类

let set = new Set();
set.add(1);
console.log(set.values()); // SetIterator {1}
console.log(set.has(1)); // true
console.log(set.size); // 1

ES6的values 方法会返回 Iterator,而不是值构成的数组,需要通过 next() 方法来遍历获取值,另外size 是一个属性不是一个方法。

可以用 delete 方法删除 set 中的元素

set.delete(1); // true

clear 方法会重置 set 数据结构,跟我们实现的功能一样。

ES6 Set类的操作

我们的Set类实现了交集、差集、并集、子集等数学操作,然后ES6 原生的Set 并没有这些功能,不过我们可以尝试模拟。

例子

let setA = new Set();
setA.add(1);
setA.add(2);
setA.add(3);
let setB = new Set();
setB.add(2);
setB.add(3);
setB.add(4);

模拟并集操作

模拟交集操作需要创建一个辅助函数,来生成包含 setA 和 setB 都有的元素的新集合。

let unionAB = new Set();

for(let x of setA) unionAB.add(x);
for(let x of setB) unionAB.add(x);
console.log(unionAB); // Set(4) {1, 2, 3, 4}

模拟交集操作

let intersection = function(SetA,SetB){
let intersectionSet = new Set();
for(let x of SetA){
if(SetB.has(x)){
intersectionSet.add(x);
}
}
return intersectionSet;
}
let intersectionSetAB = intersection(setA,setB);
console.log(intersectionSetAB); // Set(2) {2, 3}

也可以用简单的语法实现

let intersectionSetAB = new Set([x for (x of setA) if (setB.has(x))])

但是简化的函数,目前只有在火狐才可以运行。

模拟差集操作

let difference = function(SetA,SetB){
let differenceSet = new Set();
for(let x of SetA){
if(!SetB.has(x)){
differenceSet.add(x);
}
}
return differenceSet;
}
let differenceSetAB = difference(setA,setB);
console.log(differenceSetAB); // Set(1) {1}

小结

学习了从头实现一个ES6中定义的类似的Set 类,还实现了一些再其他语言的集合数据结构中不常见的一些方法,例如并集、交集、差集和子集。

后面,将会介绍散列和字典这两种非顺序数据结构。

书籍链接: 学习JavaScript数据结构与算法

为什么我要放弃javaScript数据结构与算法(第六章)—— 集合的更多相关文章

  1. 为什么我要放弃javaScript数据结构与算法(第九章)—— 图

    本章中,将学习另外一种非线性数据结构--图.这是学习的最后一种数据结构,后面将学习排序和搜索算法. 第九章 图 图的相关术语 图是网络结构的抽象模型.图是一组由边连接的节点(或顶点).学习图是重要的, ...

  2. 为什么我要放弃javaScript数据结构与算法(第二章)—— 数组

    第二章 数组 几乎所有的编程语言都原生支持数组类型,因为数组是最简单的内存数据结构.JavaScript里也有数组类型,虽然它的第一个版本并没有支持数组.本章将深入学习数组数据结构和它的能力. 为什么 ...

  3. 为什么我要放弃javaScript数据结构与算法(第一章)—— JavaScript简介

    数据结构与算法一直是我算比较薄弱的地方,希望通过阅读<javaScript数据结构与算法>可以有所改变,我相信接下来的记录不单单对于我自己有帮助,也可以帮助到一些这方面的小白,接下来让我们 ...

  4. 为什么我要放弃javaScript数据结构与算法(第十一章)—— 算法模式

    本章将会学习递归.动态规划和贪心算法. 第十一章 算法模式 递归 递归是一种解决问题的方法,它解决问题的各个小部分,直到解决最初的大问题.递归通常涉及函数调用自身. 递归函数是像下面能够直接调用自身的 ...

  5. 为什么我要放弃javaScript数据结构与算法(第十章)—— 排序和搜索算法

    本章将会学习最常见的排序和搜索算法,如冒泡排序.选择排序.插入排序.归并排序.快速排序和堆排序,以及顺序排序和二叉搜索算法. 第十章 排序和搜索算法 排序算法 我们会从一个最慢的开始,接着是一些性能好 ...

  6. 为什么我要放弃javaScript数据结构与算法(第八章)—— 树

    之前介绍了一些顺序数据结构,介绍的第一个非顺序数据结构是散列表.本章才会学习另一种非顺序数据结构--树,它对于存储需要快速寻找的数据非常有用. 本章内容 树的相关术语 创建树数据结构 树的遍历 添加和 ...

  7. 为什么我要放弃javaScript数据结构与算法(第七章)—— 字典和散列表

    本章学习使用字典和散列表来存储唯一值(不重复的值)的数据结构. 集合.字典和散列表可以存储不重复的值.在集合中,我们感兴趣的是每个值本身,并把它作为主要元素.而字典和散列表中都是用 [键,值]的形式来 ...

  8. 为什么我要放弃javaScript数据结构与算法(第五章)—— 链表

    这一章你将会学会如何实现和使用链表这种动态的数据结构,这意味着我们可以从中任意添加或移除项,它会按需进行扩张. 本章内容 链表数据结构 向链表添加元素 从链表移除元素 使用 LinkedList 类 ...

  9. 为什么我要放弃javaScript数据结构与算法(第四章)—— 队列

    有两种结构类似于数组,但在添加和删除元素时更加可控,它们就是栈和队列. 第四章 队列 队列数据结构 队列是遵循FIFO(First In First Out,先进先出,也称为先来先服务)原则的一组有序 ...

随机推荐

  1. ubuntu桌面便签 sticky note, xpad

    sudo apt-get install xpad 软件有一个选项,叫做Show window decorations,显示窗口的标题栏.

  2. webservice和wcf和web.api简单介绍

    转自:无废话的wcf等等 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下,你有很多的选择来构建一个HTTP Ser ...

  3. Selenium基础知识(详解IDE命令、css及xpath定位一)

    1. ide常用命令,参考 http://sariyalee.iteye.com/blog/1743350  2. ide介绍,参考 http://blog.csdn.net/oscar999/art ...

  4. 匹配iPhoneX

    1.header中加一下标签 <meta name="viewport" content="width=device-width,initial-scale=1,m ...

  5. 【SPJ6285 NGM2 - Another Game With Numbers】 题解

    题目链接:https://www.luogu.org/problemnew/show/SP6285 唉好久之前校内模拟赛的题目 嘴上说着明白但是实现起来我的位运算太丑陋了啊! #include < ...

  6. C# 委托系列(一)将方法作为方法的参数

    委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每 ...

  7. 工程命名为***&***出现的问题: LaunchScreen.xib: Line 20: EntityRef: expecting ';'

    今天新建一个项目命名为28 & 29. extern&static,  然后cmd + R运行,居然碰到了编译错误. 当时就奇怪了,怎么会这样呢?报错内容如下: 开始还以为新安装的Xc ...

  8. Reading Notes : 180211 概述计算机

    读书<计算机组成原理> <鸟哥的Linux私房菜 基础篇> 本章介绍电子计算机概念以及发展历史和发展趋势,内容摘自<计算机组成原理>  <鸟哥的Linux私房 ...

  9. Oracle中order by case 用法

    select * from ly_familyinformation ' ' order by case when relation = '购房人/申请人' then when relation = ...

  10. RMAN_PIPE

    涉及的dbms_pipe包中的过程和函数:(1)PACK_MESSAGE Procedures用途:Builds message in local buffer(2)SEND_MESSAGE Func ...