JavaScript数据结构之集合

JavaScript数据结构之集合

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

1.创建一个集合

目前的JavaScript实现是基于2011年6月发布的ECMAScript5.1,它包括了Array类的实现,ECMAScript6包括了set类的实现。

注意:下面实现的类就是以ECMAScript6类的实现为基础的。

Set类的骨架:

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

has(value)方法

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

即使使用对象来存储集合的值,就可以用JavaScript的in操作符来验证给定的值是否是items对象的属性。
但这个方法还有更多的实现方式,如下:

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

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

add方法

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

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

remove和clear方法

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

在remove方法中,会验证给定的value是否存在于集合中。如果存在,就从集合中移除value,返回true,表示被移除,否则返回false。

使用Set类的示例代码如下:

var set = new Set();
set.add(1);
set.add(2);

如果想要移除集合中的所有值,可以用clear方法:

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

要重置items对象,需要做的只是把一个空对象重新赋值给它。当然也可以迭代集合,用remove方法依旧移除所有的值。

size方法

下一个要实现的是 size 方法(返回集合中有多少项)。这个方法有三种实现方式。

  • 第一种方法是使用一个 length 变量,每当使用 add 或 remove 方法时控制它,就像在上一章中使用 LinkedList 类一样。

  • 第二种方法,使用JavaScript内建的 Object 类的一个内建函数(ECMAScript 5以上版本):

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

JavaScript的 Object 类有一个 keys 方法,它返回一个包含给定对象所有属性的数组。在这种情况下,可以使用这个数组的 length 属性,来返回 items 对象的属性个数。
以上代码只能在现代浏览器中运行(比如IE9以上版本、Firefox 4以上版本、Chrome 5以上版本、Opera 12以 上版本、Safari 5以上版本,等等)。

  • 第三种方法是手动提取 items 对象的每一个属性,记录属性的个数并返回这个数字。这个方法可以在任何浏览器上运行,和之前的代码是等价的:
this.sizeLegacy = function(){
	var count = 0;
	for(var prop in items) { //{5}
		if(items.hasOwnProperty(prop)) //{6}
			++count; //{7}
		}
	return count;
};

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

注意:不能简单地使用 for-in 语句遍历 items 对象的属性,递增 count 变量的值。
还需要使用 has 方法(以验证 items 对象具有该属性),因为对象的原型包含了
额外的属性(属性既有继承自JavaScript的 Object 类的,也有属于对象自身,未
用于数据结构的)。

values 方法

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

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

以上代码只能在现代浏览器中运行。既然在本书中我们使用的测试浏览器是Chrome和Firefox,代码就能工作。
如果想让代码在任何浏览器中都能执行,可以用与之前代码等价的下面这段代码:

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

遍历 items 对象的所有属性(行 {7} ),把它们添加一个数组中(行 {8} ),并返回这个数组。

使用 Set 类

现在数据结构已经完成了,看看如何使用它吧。试着执行一些命令,测试我们的 Set 类:

var set = new Set();
set.add(1);
console.log(set.values()); //输出["1"]
console.log(set.has(1)); //输出true
console.log(set.size()); //输出1
set.add(2);
console.log(set.values()); //输出["1", "2"]
console.log(set.has(2)); //true
console.log(set.size()); //2
set.remove(1);
console.log(set.values()); //输出["2"]
set.remove(2);
console.log(set.values()); //输出[]

现在有了一个和ECMAScript 6中非常类似的 Set 类实现。如前所述,也可以用数组替代对象,存储元素。

集合操作

对集合可以进行如下操作。

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

并集的数学概念,集合A和B的并集,表示为A∪B,定义如下:
A∪B = { x | x ∈ A∨x ∈ B }
现在来实现 Set 类的 union 方法:

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

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

测试一下上面的代码:

var setA = new Set();
setA.add(1);
setA.add(2);
setA.add(3);
var setB = new Set();
setB.add(3);
setB.add(4);
setB.add(5);
setB.add(6);
var unionAB = setA.union(setB);
console.log(unionAB.values());

输出为 [“1”, “2”, “3”, “4”, “5”, “6”] 。注意元素 3 同时存在于A和B中,它在结果的
集合中只出现一次。

交集

交集的数学概念,集合A和B的交集,表示为A∩B,定义如下:
A∩B = { x | x ∈ A∧x ∈ B }

现在来实现 Set 类的 intersection 方法:

this.intersection = function(otherSet){
	var intersectionSet = new Set(); //{1}
	var values = this.values();
	for (var i=0; i<values.length; i++){ //{2}
		if (otherSet.has(values[i])){ //{3}
			intersectionSet.add(values[i]); //{4}
		}
	}
	return intersectionSet;
}

intersection 方法需要找到当前 Set 实例中,所有存在于给定 Set 实例中的元素。首先创建一个新的 Set 实例,这样就能用它返回共有的元素(行 {1} )。接下来,遍历当前 Set 实例所有的值(行 {2} ),验证它们是否也存在于 otherSet 实例(行 {3} )。可以用前面实现的 has方法来验证元素是否存在于 Set 实例中。然后,如果这个值也存在于另一个 Set 实例中,就将其添加到创建的 intersectionSet 变量中(行 {4} ),最后返回它。

做些测试:

var setA = new Set();
setA.add(1);
setA.add(2);
setA.add(3);
var setB = new Set();
setB.add(2);
setB.add(3);
setB.add(4);
var intersectionAB = setA.intersection(setB);
console.log(intersectionAB.values());

输出为 [“2”, “3”] ,因为 2 和 3 同时存在于两个集合中。

差集

差集的数学概念,集合A和B的差集,表示为A —B,定义如下:
A—B = { x | x ∈ A ∧ x  B }

现在来实现 Set 类的 difference 方法:

this.difference = function(otherSet){
	var differenceSet = new Set(); //{1}
	var values = this.values();
	for (var i=0; i<values.length; i++){ //{2}
		if (!otherSet.has(values[i])){ //{3}
		
			differenceSet.add(values[i]); //{4}
		}
	}
	return differenceSet;
};

intersection 方法会得到所有同时存在于两个集合中的值。而 difference 方法会得到所有存在于集合A但不存在于B的值。因此这两个方法在实现上唯一的区别就是行 {3} 。只获取不存在于 otherSet 实例中的值,而不是也存在于其中的值。行 {1} 、 {2} 和 {4} 是完全相同的。
(用跟 intersection 部分相同的集合)做些测试:

var setA = new Set();
setA.add(1);
setA.add(2);
setA.add(3);
var setB = new Set();
setB.add(2);
setB.add(3);
setB.add(4);
var differenceAB = setA.difference(setB);
console.log(differenceAB.values());

输出为 [“1”] ,因为 1 是唯一一个仅存在于 setA 的元素。

子集

子集的数学概念,集合A是B的子集(或集合B包含了A),表示为A⊆B,定义如下:
∀x { x ∈ A → x ∈ B }

现在来实现 Set 类的 subset 方法:

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

首先需要验证的是当前 Set 实例的大小。如果当前实例中的元素比 otherSet 实例更多,它就不是一个子集(行 {1} )。子集的元素个数需要小于或等于要比较的集合。接下来要遍历集合中的所有元素(行 {2} ),验证这些元素也存在于 otherSet 中(行 {3} )。如果有任何元素不存在于 otherSet 中,就意味着它不是一个子集,返回 false (行 {4} )。如果所有元素都存在于 otherSet 中,行 {4} 就不会被执行,那么就返回 true (行 {5} )。

检验一下上面的代码效果如何:

var setA = new Set();
setA.add(1);
setA.add(2);
var setB = new Set();
setB.add(1);
setB.add(2);
setB.add(3);
var setC = new Set();
setC.add(2);
setC.add(3);
setC.add(4);
console.log(setA.subset(setB));
console.log(setA.subset(setC));
``
有三个集合: setA 是 setB 的子集(因此输出为 true ),然而 setA 不是 setC 的子集( setC只包含了 setA 中的 2 ,而不包含 1 ),因此输出为 false 。

发布了104 篇原创文章 · 获赞 252 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/low666/article/details/105033247