JavaScript数据结构——集合的实现与应用
与数学中的集合概念类似,集合由一组无序的元素组成,且集合中的每个元素都是唯一存在的。可以回顾一下中学数学中集合的概念,我们这里所要定义的集合也具有空集(即集合的内容为空)、交集、并集、差集、子集的特性。
在ES6中,原生的Set类已经实现了集合的全部特性,稍后我们会介绍它的用法。
我们使用JavaSctipt的对象来表示集合,下面是集合类的主要实现方法:
class Set {
constructor () {
this.items = {};
} add (value) { // 向集合中添加元素
if (!this.has(value)) {
this.items[value] = value;
return true;
}
return false;
} delete (value) { // 从集合中删除对应的元素
if (this.has(value)) {
delete this.items[value];
return true;
}
return false;
} has (value) { // 判断给定的元素在集合中是否存在
return this.items.hasOwnProperty(value);
} clear() { // 清空集合内容
this.items = {};
} size () { // 获取集合的长度
return Object.keys(this.items).length;
} values () { // 返回集合中所有元素的内容
return Object.values(this.items);
}
}
在使用JavaScript对象{ }来表示集合时,我们可以像操作数组一样通过[ ]来设置和获取集合内元素的值。通过这种方式,在设置集合元素的值时,如果元素不存在,则创建一个新元素,如果元素存在,则修改对应的值;在获取集合元素的值时,如果元素存在,则返回对应的值,如果元素不存在,则返回undefined。此外,JavaScript对象还提供了一些基础方法以方便我们对集合进行一些操作,例如hasOwenProperty()方法返回一个表明对象是否具有特定属性的布尔值,Object.keys()方法返回指定对象的所有属性名称的数组,Object.values()方法方法指定对象的所有属性值的数组。
上述代码很简单,这里就不再详细解释了。下面是一些测试用例和测试结果:
let set = new Set();
set.add(1);
console.log(set.values()); // [ 1 ]
console.log(set.has(1)); // true
console.log(set.size()); // set.add(2);
console.log(set.values()); // [ 1, 2 ]
console.log(set.has(2)); // true
console.log(set.size()); // set.delete(1);
console.log(set.values()); // [ 2 ] set.delete(2);
console.log(set.values()); // []
下面我们来看看集合的数学运算:并集、交集、差集、子集。
并集
对于给定的两个集合,并集返回一个包含两个集合中所有元素的新集合。示意图如下:
并集的实现代码:
union (otherSet) { // 并集
let unionSet = new Set();
this.values().forEach(value => unionSet.add(value));
otherSet.values().forEach(value => unionSet.add(value));
return unionSet;
}
首先遍历第一个集合,将所有的元素添加到新集合中,然后再遍历第二个集合,将所有的元素添加到新集合中。然后返回新集合。不用担心会添加重复的元素,因为集合的add()方法会自动排除掉已添加的元素。
测试用例及结果:
let setA = new Set();
setA.add("first");
setA.add("second");
setA.add("third"); let setB = new Set();
setB.add("third");
setB.add("fourth");
setB.add("fifth");
setB.add("sixth"); console.log(setA.union(setB).values()); // [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth' ]
交集
对于给定的两个集合,交集返回一个包含两个集合中共有元素的新集合。示意图如下:
交集的实现代码:
intersection (otherSet) { // 交集
let intersectionSet = new Set();
this.values().forEach(value => {
if (otherSet.has(value)) intersectionSet.add(value);
});
return intersectionSet;
}
遍历第一个集合,如果元素出现在第二个集合中,则将它添加到新集合。然后返回新集合。
测试用例及结果:
let setA = new Set();
setA.add("first");
setA.add("second");
setA.add("third"); let setB = new Set();
setB.add("second");
setB.add("third");
setB.add("fourth"); console.log(setA.intersection(setB).values()); // [ 'second', 'third' ]
差集
对于给定的两个集合,差集返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。示意图如下:
差集的实现代码:
difference (otherSet) { // 差集
let differenceSet = new Set();
this.values().forEach(value => {
if (!otherSet.has(value)) differenceSet.add(value);
});
return differenceSet;
}
遍历第一个集合,如果元素没有出现在第二个集合中,则将它添加到新集合。然后返回新集合。
测试用例及结果:
let setA = new Set();
setA.add("first");
setA.add("second");
setA.add("third"); let setB = new Set();
setB.add("second");
setB.add("third");
setB.add("fourth"); console.log(setA.difference(setB).values()); // [ 'first' ]
子集
验证一个给定集合是否是另一个集合的子集,即判断给定的集合中的所有元素是否都存在于另一个集合中,如果是,则这个集合就是另一个集合的子集,反之则不是。示意图如下:
子集的实现代码:
subset (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;
}
如果集合A比集合B的长度大,则直接返回false,因为这种情况A不可能是B的子集。然后使用every()函数遍历集合A的所有元素,一旦碰到其中的元素没有在集合B中出现,则直接返回false,并终止遍历。这里我们没有使用forEach来遍历集合A,是因为你无法根据某个条件来终止forEach循环。考虑下面这种情况:
var arr = ["first", "second", "third", "fourth"];
arr.forEach(item => {
if(item === "third") return true;
console.log(item);
});
输出结果是:
first
second
fourth
很显然,这里的return true语句并不能退出forEach循环,它只能保证本次循环中余下的语句不被执行,而接下来其它的元素还是会被遍历到。
在我们的subset()方法中,如果使用forEach语句,每一次都会遍历集合中的所有元素,如果遇到其中的元素没有在集合B中出现,就将isSubset变量的值设置为false,但并不能退出forEach,isSubset变量的值可能会被多次覆盖。为了提高执行效率,推荐使用every()函数,它会遍历集合中的元素,直到其中一个返回结果为false,就终止遍历,并返回false,否则就遍历所有的元素并返回true。有关every()函数的详细介绍可以看这里。与every()函数功能相似还有一个some()函数,它在遍历集合的过程中,遇到返回结果为true时就终止遍历,具体内容可以看这里。
差集的测试用例及结果:
let setA = new Set();
setA.add("first");
setA.add("second"); let setB = new Set();
setB.add("first");
setB.add("second");
setB.add("third"); let setC = new Set();
setC.add("second");
setC.add("third");
setC.add("fourth"); console.log(setA.subset(setB)); // true
console.log(setA.subset(setC)); // false
文章的开头说过,ES6提供了原生的Set类,让我们来看看它的一些使用方法:
let set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set.values()); // [Set Iterator] { 1, 2, 3 }
console.log(set.has(1)); // true
console.log(set.size); // set.delete(1);
console.log(set.values()); // [Set Iterator] { 2, 3 } set.clear();
console.log(set.values()); // [Set Iterator] { }
和前面我们自定义的Set类稍微有一点不同,values()方法返回的不是一个数组,而是Iterator迭代器。另一个就是这里的size是一个属性而不是方法,其它部分都和我们前面定义的Set类相同。由于ES6的Set类不包含对集合的数学运算,我们可以按照前面我们提供的方法来对其进行扩充。有关ES6的Set类的详细介绍可以看查看这里。
下一章我们将介绍如何用JavaScript来实现字典和散列表。
JavaScript数据结构——集合的实现与应用的更多相关文章
- JavaScript数据结构——集合、字典和散列表
集合.字典和散列表都可以存储不重复的值. 在集合中,我们感兴趣的是每个值本身,并把它当作主要元素.在字典和散列表中,我们用 [键,值] 的形式来存储数据. 集合(Set 类):[值,值]对,是一组由无 ...
- 学习javascript数据结构(三)——集合
前言 总括: 本文讲解了数据结构中的[集合]概念,并使用javascript实现了集合. 原文博客地址:学习javascript数据结构(三)--集合 知乎专栏&&简书专题:前端进击者 ...
- 为什么我要放弃javaScript数据结构与算法(第六章)—— 集合
前面已经学习了数组(列表).栈.队列和链表等顺序数据结构.这一章,我们要学习集合,这是一种不允许值重复的顺序数据结构. 本章可以学习到,如何添加和移除值,如何搜索值是否存在,也可以学习如何进行并集.交 ...
- JavaScript数据结构与算法-集合练习
集合的实现 function Set () { this.dataStore = []; this.add = add; this.remove = remove; this.size = size; ...
- 学习javascript数据结构(二)——链表
前言 人生总是直向前行走,从不留下什么. 原文地址:学习javascript数据结构(二)--链表 博主博客地址:Damonare的个人博客 正文 链表简介 上一篇博客-学习javascript数据结 ...
- 学习javascript数据结构(一)——栈和队列
前言 只要你不计较得失,人生还有什么不能想法子克服的. 原文地址:学习javascript数据结构(一)--栈和队列 博主博客地址:Damonare的个人博客 几乎所有的编程语言都原生支持数组类型,因 ...
- JavaScript数据结构——链表
链表:存储有序的元素集合,但不同于数组,链表中的元素在内存中不是连续放置的.每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成. 好处:可以添加或移除任意项,它会按需扩容 ...
- JavaScript数据结构——栈和队列
栈:后进先出(LIFO)的有序集合 队列:先进先出(FIFO)的有序集合 --------------------------------------------------------------- ...
- 学习javascript数据结构(四)——树
前言 总括: 本文讲解了数据结构中的[树]的概念,尽可能通俗易懂的解释树这种数据结构的概念,使用javascript实现了树,如有纰漏,欢迎批评指正. 原文博客地址:学习javascript数据结构( ...
随机推荐
- Java中到底是值传递还是引用传递?
Java中到底是值传递还是引用传递? 我们先回顾一下基本概念 实参和形参 参数在编程语言中是执行程序需要的数据,这个数据一般保存在变量中.在Java中定义一个方法时,可以定义一些参数, 举个例子: p ...
- 前端学习【第一篇】: HTML内容
内容概要: HTML介绍 常用标签介绍 一. HTML介绍 web服务的本质 #!/usr/bin/env python3 # _*_ coding:utf- _*_ import socket sk ...
- Python开发【第八篇】: 网络编程
内容概要 楔子 软件开发架构 网络基础 套接字(socket) 粘包 socketserver模块 一. 楔子 现在有两个python文件a.py和b.py,分别运行,这两个程序之间需要传递一个数据, ...
- Qt之股票组件-自选股--列表可以拖拽、右键常用菜单
目录 一.开头嘴一嘴 二.效果展示 三.自选股列表 1.列表初始化 2.添加Item 3.右键菜单 4.拖拽Item 5.刷新数据 四.相关文章 原文链接:Qt之股票组件-自选股--列表可以拖拽.右键 ...
- SPOJ INTSUB - Interesting Subset(数学)
http://www.spoj.com/problems/INTSUB/en/ 题意:给定一个集合,该集合由1,2,3....2n组成,n是一个整数.问该集合中有趣子集的数目,答案mod1e9+7. ...
- git简单使用-GitHub
本文描述window下如何使用git工具,操作GitHub远程代码库 一,准备工作: 1,安装git工具,一路默认next安装即可,下载地址 2,注册账号或者创建厂库(已有忽略) 注册账号后,创建仓库 ...
- 对比 C++ 和 Python,谈谈指针与引用
花下猫语:本文是学习群内 樱雨楼 小姐姐的投稿.之前已发布过她的一篇作品<当谈论迭代器时,我谈些什么?>,大受好评.本文依然是对比 C++ 与 Python,来探讨编程语言中极其重要的概念 ...
- Greenplum客户端访问控制
1. 问题描述 Greenplum默认是对客户端不开放的,即客户端要访问Greenplum数据库,需要首先开通权限. 2. 解决方案: 2.1.安装greenplum-cc-web控制台. Gp的 ...
- Java编程思想:文件读写实用工具
import java.io.*; import java.util.ArrayList; import java.util.Arrays; public class Test { public st ...
- 【基本数据结构之堆】-C++
注意:这篇博客讲的是手写堆,喜欢用C++自带数据结构模拟的慎入 今天我们来聊一聊一种奇怪 的数据结构: 堆 为什么说这个数据结构有点奇怪呢? 先看看其他的在我眼里是正常的数据结构: 队列(近似于排队) ...