首页 技术 正文
技术 2022年11月15日
0 收藏 698 点赞 3,667 浏览 3472 个字

注册列表示例

一个社交网络有一组成员,每个成员有一个存储其朋友信息的注册列表。

function Member(name){
this.name=name;
this.friends=[];
}
var a=new Member('钟二'),
b=new Member('张三'),
c=new Member('赵四'),
d=new Member('王五'),
e=new Member('阮六'),
f=new Member('耿七');
a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d,f);

搜索该网络意味着需要遍历该社交网络图。
[Effective JavaScript 笔记]第48条:避免在枚举期间修改对象
通常通过工作集来实现。工作集以单个根节点开始,然后添加发现的节点,移除访问过的节点。

for…in遍历图

使用for…in循环来实现该遍历是很方便的。

Member.prototype.inNetwork=function(other){
var visited={};
var workset={};
workset[this.name]=this;
for(var name in workset){
var member=workset[name];
delete workset[name];
if(name in visited){
continue;
}
visited[name]=member;
if(member===other){
return true;
}
member.friends.forEach(function(friend){
workset[friend.name]=friend;
});
}
return false;
}

上面代码有什么问题嘛?在大多环境下可以工作,但有一些环境这段代码就不能工作了。

a.inNetwork(f);//false

这里为什么呢。这里说明for…in循环在运行时出错了错误,并没有要求枚举对象的修改与当前保持一致。事实上,ES对并发修改在不同js环境下的行为的规范留有余地。标准规定:
如果被枚举的对象在枚举期间添加了新的属性,那么在枚举期间并不能保证新添加的属性能被访问。
上面规范的实际后果:如果我们修改了被枚举的对象,则不能保证for…in循环的行为是可预见的。

另一种遍历图

自己管得循环控制。当使用循环时,应该使用自己的字典抽象以避免原型污染。可以将字典放置在WorkSet类中来追踪当前集合中的元素数量。

function WorkSet(){
this.entries=new Dict();
this.count=0;
}WorkSet.prototype.isEmpty=function(){
return this.count===0;
};
WorkSet.prototype.add=function(key,val){
if(this.entries.has(key)){
return;
}
this.entries.set(key,val);
this.count++;
};
WorkSet.prototype.get=function(key){
return this.entries.get(key);
};
WorkSet.prototype.remove=function(key){
if(!this.entries.has(key)){
return;
}
this.entries.remove(key);
this.count--;
};

为了提取集合的任意一个元素,给Dict类添加一个新方法

Dict.prototype.pick=function(){
for(var key in this.elements){
if(this.has(key)){
return key;
}
}
throw new Error('empty dictionary');
};
WorkSet.prototype.pick=function(){
return this.entries.pick();
};

下面改写上一版本的inNetwork方法,这里使用while来循环。每次选择任意一个元素并从工作集中删除。

Member.prototype.inNetwork=function(other){
var visited={};
var workset=new WorkSet();
workset.add(this.name,this);
while(!workset.isEmpty()){
var name=workset.pick();
var member=workset.get(name);
workset.remove(name);
if(name in visited){
continue;
}
visited[name]=member;
if(member === other){
return true;
}
member.friends.forEach(function(friend){
workset.add(friend.name,friend);
})
}
return false;
};

其中pick方法是一个不确定性的例子。不确定性是指一个操作并不能保证使用语言的主义产生一个单一的可预见的结果。这个不确定性是因为for…in循环可能在不同的js环境中选择不同的枚举顺序。使用不确定性可能会使你的程序引入一个不可预测的元素。测试可能在某个平台通过,某些平台不通过,或同一平台不同时候,结果也可能不同。

工用列表算法

不确定性的来源是难以避免的,考虑使用一个确定的工作集算法替代方案。即工作列表算法。将工作条目存储到数组中而不是集合中,则inNetwork方法,将总是以相同的顺序遍历图。

Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member.name in visited){
continue;
}
visited[memeber.name]=member;
if(member === other){
return true;
}
member.friends.forEach(function(friend){
worklist.push(friend);
})
}
return false;
};

这一版本inNetwork方法会确定性地添加和删除工作条目。无论发现什么路径,该方法对于连接的成员总是返回true,所以最终结果是一样的。

提示

  • 当使用for…in循环枚举一个对象的属性时,确保不要修改对象

  • 当迭代一个对象时,如果该对象的内容可能会在循环期间被改变,应该使用while循环或经典的for循环来代替for…in循环

  • 为了在不断变化的数据结构中能够预测枚举,考虑使用一个有序的数据结构,例如数组,而不要使用字典

附录:示例完整版

function Member(name){
this.name=name;
this.friends=[];
}
Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member.name in visited){
continue;
}
visited[member.name]=member;
if(member === other){
return true;
}
member.friends.forEach(function(friend){
worklist.push(friend);
})
}
return false;
};//测试代码
var a=new Member('钟二'),
b=new Member('张三'),
c=new Member('赵四'),
d=new Member('王五'),
e=new Member('阮六'),
f=new Member('耿七');
a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d,f);a.inNetwork(f);//true
a.inNetwork(d);//true
f.inNetwork(a);//false
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,082
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,557
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,406
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,179
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,815
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,898