【数据结构与算法】集合、字典(JavaScript版详解)

By yesmore on 2021-08-14
阅读时间 11 分钟
文章共 2.5k
阅读量

要点:JavaScript的集合(set)、字典(map)概念

一、集合

几乎每种编程语言中,都有集合结构。集合比较常见的实现方式是哈希表,这里使用 JavaScript 的 Object 进行封装。

1.1 源码定义

1
2
3
4
5
6
7
8
interface Set<T> {
add(value: T): this;
clear(): void;
delete(value: T): boolean;
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void;
has(value: T): boolean;
readonly size: number;
}

1.2 集合特点

1、集合通常是由一组无序的不能重复的元素构成。

2、数学中常指的集合中的元素是可以重复的,但是计算机中集合的元素不能重复。

3、集合是特殊的数组:

  • 特殊之处在于里面的元素没有顺序,也不能重复
  • 没有顺序意味着不能通过下标值进行访问,不能重复意味着相同的对象在集合中只会存在一份。

1.3 封装集合

ES6 中的 Set 就是一个集合类,这里我们重新封装一个 Set 类,了解集合的底层实现。

集合常见的操作

add(value) 向集合添加一个新的项。

remove(value) 从集合移除一个值。

has(value) 如果值在集合中,返回 true,否则返回 false

clear() 移除集合中的所有项。

size() 返回集合所包含元素的数量。与数组的 length 属性类似。

values() 返回一个包含集合中所有值的数组。

还有其他的方法,用的不多,这里不做封装。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 集合结构的封装
class Set {
constructor() {
this.items = {};
}

// has(value) 判断集合中是否存在 value 值,存在返回 true,否则返回 false
has(value) {
return this.items.hasOwnProperty(value);
}

// add(value) 往集合中添加 value
add(value) {
if (this.has(value)) return false;
this.items[value] = value;
return true;
}

// remove(value) 删除集合中指定的 value
remove(value) {
// 如果集合不存在该 value,返回 false
if (!this.has(value)) return false;
delete this.items[value];
}

// clear() 清空集合中所有 value
clear() {
this.items = {};
}

// size() 获取集合中的 value 个数
size() {
return Object.keys(this.items).length;
}

// values() 获取集合中所有的 value
values() {
return Object.keys(this.items);
}
}

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const set = new Set();

// add() 测试
set.add("abc");
set.add("abc");
set.add("123");
set.add("zxc");
console.log(set); //--> {items: {123: "123", abc: "abc", zxc: "zxc"}}

// has() 测试
console.log(set.has("123")); //--> true
console.log(set.has("456")); //--> false

// remove() 测试
set.remove("abc");
console.log(set); //--> {items: {123: "123", zxc: "zxc"}}

// size() 测试
console.log(set.size()); //--> 2

// values() 测试
console.log(set.values()); //--> ["123", "zxc"]

// clear() 测试
set.clear();
console.log(set.values()); //--> []

1.4 集合间的操作

并集:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。

交集:对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。

差集:对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。

子集:验证一个给定集合是否是另一个集合的子集。

image

并集的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// union() 求两个集合的并集
union(otherSet) {
// 1、创建一个新集合
let unionSet = new Set();

// 2、将当前集合(this)的所有 value,添加到新集合(unionSet)中
for (let value of this.values()) {
unionSet.add(value);
}

// 3、将 otherSet 集合的所有 value,添加到新集合(unionSet)中
for (let value of otherSet.values()) {
unionSet.add(value); // add() 已经有重复判断
}

return unionSet;
}

交集的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// intersection() 求两个集合的交集
intersection(otherSet) {

// 1、创建一个新集合
let intersectionSet = new Set();

// 2、从当前集合中取出每一个 value,判断是否在 otherSet 集合中存在
for (let value of this.values()) {
if (otherSet.has(value)) {
intersectionSet.add(value);
}
}

return intersectionSet;
}

差集的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// difference() 差集
difference(otherSet) {

// 1、创建一个新集合
let differenceSet = new Set();

// 2、从当前集合中取出每一个 value,判断是否在 otherSet 集合中存在,不存在的即为差集
for (let value of this.values()) {
if (!otherSet.has(value)) {
differenceSet.add(value);
}
}

return differenceSet;
}

子集的实现

1
2
3
4
5
6
7
8
9
10
11
12
// subset() 子集
subset(otherSet) {

// 从当前集合中取出每一个 value,判断是否在 otherSet 集合中存在,有不存在的返回 false
// 遍历完所有的,返回 true
for (let value of this.values()) {
if (!otherSet.has(value)) {
return false;
}
}
return true;
}

1.5 集合的完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 集合结构的封装
export default class Set {
constructor() {
this.items = {};
}

// has(value) 判断集合中是否存在 value 值,存在返回 true,否则返回 false
has(value) {
return this.items.hasOwnProperty(value);
}

// add(value) 往集合中添加 value
add(value) {
if (this.has(value)) return false;
this.items[value] = value;
return true;
}

// remove(value) 删除集合中指定的 value
remove(value) {
// 如果集合不存在该 value,返回 false
if (!this.has(value)) return false;
delete this.items[value];
}

// clear() 清空集合中所有 value
clear() {
this.items = {};
}

// size() 获取集合中的 value 个数
size() {
return Object.keys(this.items).length;
}

// values() 获取集合中所有的 value
values() {
return Object.keys(this.items);
}

// ------- 集合间的操作 ------- //
// union() 求两个集合的并集
union(otherSet) {
// 1、创建一个新集合
let unionSet = new Set();

// 2、将当前集合(this)的所有 value,添加到新集合(unionSet)中
for (let value of this.values()) {
unionSet.add(value);
}

// 3、将 otherSet 集合的所有 value,添加到新集合(unionSet)中
for (let value of otherSet.values()) {
unionSet.add(value); // add() 已经有重复判断
}

return unionSet;
}

// intersection() 求两个集合的交集
intersection(otherSet) {
// 1、创建一个新集合
let intersectionSet = new Set();

// 2、从当前集合中取出每一个 value,判断是否在 otherSet 集合中存在
for (let value of this.values()) {
if (otherSet.has(value)) {
intersectionSet.add(value);
}
}

return intersectionSet;
}

// difference() 差集
difference(otherSet) {
// 1、创建一个新集合
let differenceSet = new Set();

// 2、从当前集合中取出每一个 value,判断是否在 otherSet 集合中存在,不存在的即为差集
for (let value of this.values()) {
if (!otherSet.has(value)) {
differenceSet.add(value);
}
}

return differenceSet;
}

// subset() 子集
subset(otherSet) {
// 从当前集合中取出每一个 value,判断是否在 otherSet 集合中存在,有不存在的返回 false
// 遍历完所有的,返回 true
for (let value of this.values()) {
if (!otherSet.has(value)) {
return false;
}
}
return true;
}
}

二、字典

2.0 源码定义

1
2
3
4
5
6
7
8
9
interface Map<K, V> {
clear(): void;
delete(key: K): boolean;
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
get(key: K): V | undefined;
has(key: K): boolean;
set(key: K, value: V): this;
readonly size: number;
}

2.1 字典特点

1、字典存储的是键值对,主要特点是一一对应

2、比如保存一个人的信息

  • 数组形式:[19,"Tom", 1.65],可通过下标值取出信息。
  • 字典形式:{"age": 19, "name": "Tom", "height": 165},可以通过 key 取出 value

3、此外,在字典中 key 是不能重复且无序的,而 Value 可以重复

2.2 字典和映射的关系

有些编程语言中称这种映射关系为字典,如 Swift 中的 Dictonary,Python 中的 dict

有些编程语言中称这种映射关系为 Map,比如 Java 中的 HashMapTreeMap 等。

2.3 字典常见的操作

set(key,value) 向字典中添加新元素。

remove(key) 通过使用键值来从字典中移除键值对应的数据值。

has(key) 如果某个键值存在于这个字典中,则返回 true,反之则返回 false

get(key) 通过键值查找特定的数值并返回。

clear() 将这个字典中的所有元素全部删除。

size() 返回字典所包含元素的数量。与数组的 length 属性类似。

keys() 将字典所包含的所有键名以数组形式返回。

values() 将字典所包含的所有数值以数组形式返回。

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (var key of myMap.keys()) {
console.log(key);
}

for (var value of myMap.values()) {
console.log(value);
}

for (var [key, value] of myMap) {
console.log(key + " = " + value);
}

for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}

myMap.forEach(function(value, key) {
console.log(key + " = " + value);
}, myMap)

特殊说明

任何一个NaN 都不等于 NaN, 但是NaN可以作为Map的键,而且是唯一的。
undefined等于undefined ; 所有变量初始值都为undefined

1
2
3
4
5
6
7
var myMap = new Map();
myMap.set(NaN, "not a number");
console.log(myMap.get(NaN));
// "not a number"
var otherNaN = Number("foo");
console.log(myMap.get(otherNaN));
// "not a number"

2.4 字典封装

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 字典结构的封装
export default class Map {
constructor() {
this.items = {};
}

// has(key) 判断字典中是否存在某个 key
has(key) {
return this.items.hasOwnProperty(key);
}

// set(key, value) 在字典中添加键值对
set(key, value) {
this.items[key] = value;
}

// remove(key) 在字典中删除指定的 key
remove(key) {
// 如果集合不存在该 key,返回 false
if (!this.has(key))
return false;
delete this.items[key];
}

// get(key) 获取指定 key 的 value,如果没有,返回 undefined
get(key) {
return this.has(key) ? this.items[key] : undefined;
}

// 获取所有的 key
keys() {
return Object.keys(this.items);
}

// 获取所有的 value
values() {
return Object.values(this.items);
}

// size() 获取字典中的键值对个数
size() {
return this.keys().length;
}

// clear() 清空字典中所有的键值对
clear() {
this.items = {};
}
}

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const map = new Map();

// set() 测试
map.set("name", "XPoet");
map.set("age", 18);
map.set("email", "i@xpoet.cn");
console.log(map); // {items: {name: "XPoet", age: 18, email: "i@xpoet.cn"}}

// has() 测试
console.log(map.has("name")); //--> true
console.log(map.has("address")); //--> false

// remove() 测试
map.remove("name");
console.log(map); // {age: 18, email: "i@xpoet.cn"}

// get() 测试
console.log(map.get("age")); //--> 18

// keys() 测试
console.log(map.keys()); //--> ["age", "email"]

// values() 测试
console.log(map.values()); //--> [18, "i@xpoet.cn"]

// size() 测试
console.log(map.size()); //--> 2

Tips: Please indicate the source and original author when reprinting or quoting this article.