数据结构:用JS模拟Set类的实现

1,296 阅读4分钟

首先,我们先介绍一下集合。 集合是由一组无序且唯一(即不能重复)的项组成的。 我们可以把集合想象成一个既没有重复元素,也没有顺序概念的数组。 接下来,我们基于上面的定义来创建Set类:

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

当然,我们还需要声明一些集合可用的方法。

  • add(value):向集合中添加一个新的项。
  • remove(value):从集合中移除一个值。
  • has(value):判断集合中是否有对应值。
  • clear():移除集合中所有项。
  • size():返回集合所包含元素的数量。
  • values():返回一个包含集合中所有值的数组。

has(value)方法

我们先来实现has(value)方法,因为后面方法的实现需要调用它。

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

因为我们使用对象来存储集合的值,所以这里就直接可以使用JavaScript的in操作符来进行判断。 当然,我们还有更好的实现方式:

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

所有JavaScript对象都有hasOwnProperty方法,它返回一个表明对象是否具有特定属性的布尔值。 :hasOwnProperty方法可用于判断对应属性是否属于对象本身,能把对象原型链上属性排除。

add方法

this.add = function (value) {
	//检查value是否在集合中
	if (!this.has(value)) {
		items[value] = value;
		return true;
	}
	return false;
};

remove方法

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

clear方法

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

size方法

这里我们实现size方法(返回集合中有多少项)有三种方式。

第一种是在内部使用length变量,每当调用add或remove方法时对它进行操作,size方法对length进行返回。

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

这里我们使用Object类的内建函数key方法,它返回一个包含给定对象所有属性的数组,然后我们通过返回数组的length属性来实现size方法。

第三种

this.sizeLegacy = function () {
	var count = 0;
	for (var prop in items) {
		if (items.hasOwnProperty(prop)) {
			++count;
		}
	}
	return count;
};

这里我们手动提取items对象的每一个属性,记录属性个数并返回这个数字。这个方法可以在任何浏览器上运行。

values方法

this.values = function () {
	return Object.keys(items);
};

如果要想它在任意浏览器运行,可以使用下面代码:

this.valuesLegacy = function () {
	var keys = [];
	for (var key in items) {
		if (items.hasOwnProperty(key)) {
			keys.push(key);
		}
	}
	return keys;
};

然后,我们来实现Set类一些集合的操作。

并集

对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。 在数学中,集合A和集合B的并集,表示为A ∪ B。 A∪B = {x∣x∈A∨x∈B} 即x元素存在于集合A中,或x存在于集合B中。

这里写图片描述

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

创建一个新的集合,代表两个集合的并集。然后把两集合中的值遍历添加到新的集合中。

交集

这里写图片描述
集合A和集合B的交集,表示为A∩B: A∩B = {x∣x∈A∧x∈B} 元素x存在于A中,且元素x存在于B中。

this.intersection = function (otherSet) {
	var intersectionSet = new Set();

	var values = this.values();
	for (var i = 0; i < values.length; i++) {
		if (otherSet.has(values[i])) {
			intersectionSet.add(values[i]);
		}
	}

	return intersectionSet;
};

创建一个新的Set实例,遍历当前Set实例所有的值,验证对应值是否同时存在于另一个Set实例中,如果是,将其添加到新创建的Set实例中,最后将其返回。

差集

集合A和集合B的差集,表示为A-B: A-B = {x∣x∈A∧x∉B} 元素x存在于集合A中,且x不存在于集合B中。

这里写图片描述

this.difference = function () {
	var differenceSet = new Set();
	
	var values = this.values();
	for (var i = 0; i < values.length; i++) {
		if (!otherSet.has(values[i])) { //只获取不存在于otherSet实例中的值
			differenceSet.add(values[i]);
		}
	}
	return differenceSet;
};

子集

集合A是集合B的子集,表示为A⊆B: ∀x{x∈A→x∈B} 集合A中的每一个x元素,也在集合B中。

这里写图片描述

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

首先验证当前Set实例中元素个数是否小于otherSet实例中元素个数。 接下来遍历当前Set实例中元素是否也都存在于otherSet实例中。

现在,我们实现了类似于ECMAScript 6中的Set类。 如果你有什么好的想法或是意见请写在下方评论区,也可以私信我。 我也会对这篇博文随时间进行修正。