我只是一个在沙滩上捡贝壳的小男孩,梦想有一天可以发现知识的真理~~~

列表

在日常生活中,人们经常使用列表:待办事项列表、购物清单、十佳榜单、最后十名榜单等。计算机程序也在使用列表,尤其是列表中保存的元素不是太多时。当不需要在一个很长的序列中查找元素,或者对其进行排序时,列表显得尤为有用。反之,如果数据结构非常复杂,列表的作用就没有那么大了

列表的抽象数据类型定义

  • 列表是一组有序的数据
  • 每个列表中的数据项称为元素
  • 在JavaScript中,列表中的元素可以是任意数据类型
  • 列表中可以保存多少元素并没有事先限定,实际使用时元素的数量受到程序内存的限制
  • 不包含任何元素的列表称为空列表。列表中包含元素的个数称为列表的length
  • 在内部实现上,用一个变量listSize保存列表中元素的个数
  • 可以在列表末尾append一个元素,也可以在一个给定元素后或列表的起始位置insert一个元素
  • 使用remove方法从列表中删除元素,使用clear方法清空列表中所有的元素
  • 还可以使用toString()方法显示列表中所有的元素,使用getElement()方法显示当前元素
  • 列表拥有描述元素位置的属性
  • 列表有前有后(分别对应front和end)
  • 使用next()方法可以从当前元素移动到下一个元素,使用prev()方法可以移动到当前元素的前一个元
  • 还可以使用moveTo(n)方法直接移动到指定位置,这里的n表示要移动到第n个位置
  • currPos属性表示列表中的当前位置
列表的抽象数据类型并未指明列表的存储结构,我们使用一个数组dataStore来存储元素
 
 

列表的抽象数据类型定义

listSize(属性) 列表的元素个数
pos  (属性) 列表的当前位置
length  (属性) 返回列表中元素的个数
clear  (方法) 清空列表中的所有元素
toString  (方法) 返回列表的字符串形式
getElement  (方法) 返回当前位置的元素
insert  (方法) 在现有元素后插入新元素
append  (方法) 在列表的末尾添加新元素
remove  (方法) 从列表中删除元素
front  (方法) 将列表的当前位置设移动到第一个元素
end  (方法) 将列表的当前位置移动到最后一个元素
prev(方法) 将当前位置后移一位
next  (方法) 将当前位置前移一位
currPos  (方法) 返回列表的当前位置
moveTo(方法) 将当前位置移动到指定位置
实现列表类
根据上面定义的列表抽象数据类型,可以直接实现一个List类。让我们从定义构造函数开始,虽然它本身并不是列表抽象数据类型定义的一部分:
  1. function  List(){
  2. this.listSize =0;
  3. this.pos =0;
  4. this.dataStore =[];//初始化一个空数组来保存列表元素
  5. this.clear = clear;
  6. this.find = find;
  7. this.toString = toString;
  8. this.insert = insert;
  9. this.append = append;
  10. this.remove = remove;
  11. this.front = front;
  12. this.end = end;
  13. this.prev = prev;
  14. this.next = next;
  15. this.length = length;
  16. this.currPos = currPos;
  17. this.moveTo = moveTo;
  18. this.getElement = getElement;
  19. this.length = length;
  20. this.contains = contains;
  21. }
 
append:给列表添加元素
我们要实现的第一个方法是append(),该方法给列表的下一个位置增加一个新的元素,这
个位置刚好等于变量listSize的值:
  1. function append(element){
  2. this.dataStore[this.listSize++]= element;
  3. }
当新元素就位后,变量listSize加1。
remove:从列表中删除元素
接下来,让我们看看如何从列表中删除一个元素。remove()方法是cList类中较难实现的
一个方法。首先,需要在列表中找到该元素,然后删除它,并且调整底层的数组对象以填
补删除该元素后留下的空白。好消息是,可以使用splice()方法简化这一过程。让我们先
从一个辅助方法find()开始,该方法用于查找要删除的元素:
  1. function find(element){
  2.  for(var i =0; i <this.dataStore.length;++i){
  3.   if(this.dataStore[i]== element){
  4.   return i;
  5. }
  6. }
  7. return-1;
  8. }
 
find:在列表中查找某一元素
find()方法通过对数组对象dataStore进行迭代,查找给定的元素。如果找到,就返回该元素在列表中的位置,否则返回-1,这是在数组中找不到指定元素时返回的标准值。我们可以在remove()方法中利用此值做错误校验。
remove()方法使用find()方法返回的位置对数组dataStore进行截取。数组改变后,将变
量listSize的值减1,以反映列表的最新长度。如果元素删除成功,该方法返回true,否
则返回false。代码如下所示:
  1. function remove(element){
  2. var foundAt =this.find(element);
  3. if(foundAt >-1){
  4. this.dataStore.splice(foundAt,1);
  5. --this.listSize;
  6. returntrue;
  7. }
  8. returnfalse;
  9. }
 
length:列表中有多少个元素
length()方法返回列表中元素的个数:
  1. function length(){
  2.   returnthis.listSize;
  3. }
 
toString:显示列表中的元素
现在是时候创建一个方法,用来显示列表中的元素了。下面是一段简单的代码,实现了
toString()方法:
  1. function toString(){
  2.   returnthis.dataStore;
  3. }
严格说来,该方法返回的是一个数组,而不是一个字符串,但它的目的是为了显示列表的
当前状态,因此返回一个数组就足够了。
让我们暂且从实现cList类的工作中偷得浮生半日闲,来看看这个类目前表现如何。下面
是一个简短的测试代码,检验了我们之前创建的方法:
  1. var names =newList();
  2. names.append("Cynthia");
  3. names.append("Raymond");
  4. names.append("Barbara");
  5. print(names.toString());
  6. names.remove("Raymond");
  7. print(names.toString());
该程序的输出为:
Cynthia,Raymond,Barbara
Cynthia,Barbara
 
insert:向列表中插入一个元素
接下来要讨论的方法是insert()。如果在前面的列表中删除了Raymond,但是现在又想将
它放回原来的位置,该怎么办?insert()方法需要知道将元素插入到什么位置,因此现在
我们假设插入是指插入到列表中某个元素之后。知道了这些,就可以定义insert()方法了:
  1. function insert(element, after){
  2. var insertPos =this.find(after);
  3.  if(insertPos >-1){
  4.   this.dataStore.splice(insertPos+1,0, element);
  5.    ++this.listSize;
  6.    return true;
  7. }
  8. returnfalse;
  9. }
在实现中,insert()方法用到了find()方法,find()方法会寻找传入的after参数在列表中的位置,找到该位置后,使用splice()方法将新元素插入该位置之后,然后将变量listSize加1并返回true,表明插入成功。
 
clear:清空列表中所有的元素
接下来,我们需要一个方法清空列表中的所有元素,为插入新元素腾出空间:
  1. function clear(){
  2. deletethis.dataStore;
  3. this.dataStore =[];
  4. this.listSize =this.pos =0;
  5. }
clear()方法使用delete操作符删除数组dataStore,接着在下一行创建一个空数组。最后一行将listSize和pos的值设为1,表明这是一个新的空列表。
 
contains:判断给定值是否在列表中
当需要判断一个给定值是否在列表中时,contains()方法就变得很有用。下面是该方法的
定义:
  1. function contains(element){
  2. for(var i =0; i <this.dataStore.length;++i){
  3. if(this.dataStore[i]== element){
  4.     return true;
  5. }
  6. }
  7. returnfalse;
  8. }
 
遍历列表
最后的一组方法允许用户在列表上自由移动,最后一个方法getElement()返回列表的当前
元素:
  1. function front(){
  2.   this.pos =0;
  3. }
  4. function end(){
  5.   this.pos =this.listSize-1;
  6. }
  7. function prev(){
  8. if(this.pos >0){
  9. --this.pos;
  10. }
  11. }
  12. function next(){
  13. if(this.pos <this.listSize-1){
  14. ++this.pos;
  15. }
  16. }
  17. function currPos(){
  18. returnthis.pos;
  19. }
  20. function moveTo(position){
  21. this.pos = position;
  22. }
  23. function getElement(){
  24. returnthis.dataStore[this.pos];
  25. }
 
让我们创建一个由姓名组成的列表,来展示怎么使用这些方法:
  1. var names =newList();
  2. names.append("Clayton");
  3. names.append("Raymond");
  4. names.append("Cynthia");
  5. names.append("Jennifer");
  6. names.append("Bryan");
  7. names.append("Danny");
现在移动到列表中的第一个元素并且显示它:
names.front();
print(names.getElement()); //显示Clayton
接下来向前移动一个单位并且显示它:
names.next();
print(names.getElement()); //显示Raymond
现在,让我们先向前移动两次,然后向后移动一次,显示出当前元素,看看prev()方法的工作原理:
names.next();
names.next();
names.prev();
print(names.getElement()); //显示Cynthia
上述代码段展示的这些行为实际上是迭代器的概念,这也是接下来要讨论的内容。
 
使用迭代器访问列表
使用迭代器,可以不必关心数据的内部存储方式,以实现对列表的遍历。前面提到的方法
front()、end()、prev()、next()和currPos就实现了cList类的一个迭代器。以下是和使
用数组索引的方式相比,使用迭代器的一些优点。
  • 访问列表元素时不必关心底层的数据存储结构 
  • 当为列表添加一个元素时,索引的值就不对了,此时只用更新列表,而不用更新迭代器。
  • 可以用不同类型的数据存储方式实现 
  • cList类,迭代器为访问列表里的元素提供了一种统一的方式
了解了这些优点后,来看一个使用迭代器遍历列表的例子:
  1. for(names.front(); names.currPos()< names.length(); names.next()){
  2. print(names.getElement());
  3. }
在for循环的一开始,将列表的当前位置设置为第一个元素。只要currPos的值小于列表的长度,就一直循环,每一次循环都调用next()方法将当前位置向前移动一位。
同理,还可以从后向前遍历列表,代码如下:
  1. for(names.end(); names.currPos()>=0; names.prev()){
  2. print(names.getElement());
  3. }
循环从列表的最后一个元素开始,当当前位置大于或等于0时,调用prev()方法后移一位。迭代器只是用来在列表上随意移动,而不应该和任何为列表增加或删除元素的方法一起使用
 

一个基于列表的应用

为了展示如何使用列表,我们将实现一个类似Redbox的影碟租赁自助查询系统。
读取文本文件
为了得到商店内的影碟清单,我们需要将数据从文件中读进来。首先,使用一个文本编辑
器输入现有影碟清单,假设将该文件保存为films.txt。该文件的内容如下(这是由IMDB
用户在2013年10月5日选出的20部最佳影片)。
(1) The Shawshank Redemption(《肖申克的救赎》)
(2) The Godfather(《教父》)
(3) The Godfather: Part II(《教父2》)
(4) Pulp Fiction(《低俗小说》)
(5) The Good, the Bad and the Ugly(《黄金三镖客》)
(6) 12 Angry Men(《十二怒汉》 )
(7) Schindler’s List(《辛德勒名单》)
(8) The Dark Knight(《黑暗骑士》)
(9) The Lord of the Rings: The Return of the King(《指环王:王者归来》)
(10) Fight Club(《搏击俱乐部》)
(11) Star Wars: Episode V - The Empire Strikes Back(《星球大战5:帝国反击战》)
(12) One Flew Over the Cuckoo’s Nest(《飞越疯人院》)
(13) The Lord of the Rings: The Fellowship of the Ring(《指环王:护戒使者》)
(14) Inception(《盗梦空间》)
(15) Goodfellas(《好家伙》)
(16) Star Wars(《星球大战》)
(17) Seven Samurai(《七武士》)
(18) The Matrix(《黑客帝国》)
(19) Forrest Gump(《阿甘正传》)
(20) City of God(《上帝之城》)
现在,我们需要一段程序来读取文件内容:
var movies = read(films.txt).split("\n");
这一行代码做了两件事。首先,它通过调用函数read(films.txt)读取了文本文件的内容;其次,它将读进来的内容按照换行符分成了不同行,然后保存到数组movies中。这行程序挺管用,但还谈不上完美。当读进来的内容被分割成数组后,换行符被替换成空格。多一个空格看起来无伤大雅,但是在比较字符串时却是个灾难。因此,我们需要在循环里,使用trim()方法删除每个数组元素末尾的空格。要是有一个函数能把这些操作封装起来那是再好不过了,那就让我们定义一个这样的方法吧。从文件中读入数据,然后将结果保存到一个数组中:
  1. function createArr(file){
  2. var arr = read(file).split("\n");
  3. for(var i =0; i < arr.length;++i){
  4. arr[i]= arr[i].trim();
  5. }
  6. return arr;
  7. }
 
使用列表管理影碟租赁
下一步要将数组movies中的元素保存到一个列表中。代码如下:
  1. var movieList =newList();
  2. for(var i =0; i < movies.length;++i){
  3. movieList.append(movies[i]);
  4. }
 
现在可以写一个函数来显示影碟店里现有的影碟清单了:
  1. function displayList(list){
  2. for(list.front(); list.currPos()< list.length(); list.next()){
  3. print(list.getElement());
  4. }
  5. }
 
displayList()函数对于原生的数据类型没什么问题,比如由字符串组成的列表。但是它用不了自定义类型,比如我们将在下面定义的Customer对象。让我们对它稍作修改,让它可以发现列表是由Customer对象组成的,这样就可以对应地对其进行显示了。下面是重新
定义的displayList()函数:
  1. function displayList(list){
  2. for(list.front(); list.currPos()< list.length(); list.next()){
  3. if(list.getElement() instanceof Customer){
  4. print(list.getElement()["name"]+", "+
  5. list.getElement()["movie"]);
  6. }
  7. else{
  8. print(list.getElement());
  9. }
  10. }
  11. }
对于列表中的每一个元素,都使用instanceof操作符判断该元素是否是Customer对象。
如果是,就使用name和movie做索引,得到客户检出的相应条目的值;如果不是,返回该
元素即可
                         
现在已经有了列表movies,还需要创建一个新列表customers,用来保存在系统中检出电
影的客户:
var customers = new List();
该列表包含Customer对象,该对象由用户的姓名和用户检出的电影组成。下面是Customer
对象的构造函数:
  1. functionCustomer(name, movie){
  2. this.name = name;
  3. this.movie = movie;
  4. }
接下来,需要创建一个允许客户检出电影的函数。该函数有两个参数:客户姓名和客户想要检出的电影。如果该电影目前可以租赁,该方法会从影碟店的影碟清单里删除该元素,同时加入客户列表customers。这个操作会用到列表的contains()方法
下面是用于检出电影的函数定义:
  1. function checkOut(name, movie, filmList, customerList){
  2. if(movieList.contains(movie)){
  3. var c =newCustomer(name, movie);
  4. customerList.append(c);
  5. filmList.remove(movie);
  6. }
  7. else{
  8. print(movie +" is not available.");
  9. }
  10. }
 
该方法首先查询想要租赁的电影是否存在,如果可以,就创建一个新的Customer对象,该对象包含影片名称和客户姓名。然后将该对象加入客户列表,并且从影碟列表中删除该影片。如果影片暂时不存在,则显示一行简短的提示
可以用下列简单代码测试checkOut()函数:
  1. var movies = createArr("films.txt");
  2. var movieList =newList();
  3. var customers =newList();
  4. for(var i =0; i < movies.length;++i){
  5. movieList.append(movies[i]);
  6. }
  7. print("Available movies: \n");
  8. displayList(movieList);
  9. checkOut("Jane Doe","The Godfather", movieList, customers);
  10. print("\nCustomer Rentals: \n");
  11. displayList(customers);
输出显示"The Godfather"从影碟列表中删除了,跟着又被加入了客户列表中。
让我们给程序加些标题,让输出更易阅读,同时再加上一点带有交互性质的输入:
  1. var movies = createArr("films.txt");
  2. var movieList =newList();
  3. var customers =newList();
  4. for(var i =0; i < movies.length;++i){
  5. movieList.append(movies[i]);
  6. }
  7. print("Available movies: \n");
  8. displayList(movieList);
  9. putstr("\nEnter your name: ");
  10. var name = readline();
  11. putstr("What movie would you like? ");
  12. var movie = readline();
  13. checkOut(name, movie, movieList, customers);
  14. print("\nCustomer Rentals: \n");
  15. displayList(customers);
  16. print("\nMovies Now Available\n");
  17. displayList(movieList);
  18. 程序输出如下:

Available movies:
TheShawshankRedemption
TheGodfather
TheGodfather:Part II
PulpFiction
TheGood, the Bad and the Ugly
12AngryMen
Schindler's List
TheDarkKnight
TheLord of the Rings:TheReturn of the King
FightClub
StarWars:Episode V -TheEmpireStrikesBack
OneFlewOver the Cuckoo's Nest
TheLord of the Rings:TheFellowship of the Ring
Inception
Goodfellas
StarWars
SevenSamurai
TheMatrix
ForrestGump
City of God
Enter your name:JaneDoe
What movie would you like?TheGodfather
CustomerRentals:
JaneDoe,TheGodfather
MoviesNowAvailable
TheShawshankRedemption
TheGodfather:Part II
PulpFiction
TheGood, the Bad and the Ugly
12AngryMen
Schindler's List
TheDarkKnight
TheLord of the Rings:TheReturn of the King
FightClub
StarWars:Episode V -TheEmpireStrikesBack
OneFlewOver the Cuckoo's Nest
TheLord of the Rings:TheFellowship of the Ring
Inception
Goodfellas
StarWars
SevenSamurai
TheMatrix
ForrestGump
City of God

 
 
posted @ 2017-05-12 11:44  瓜不甜  阅读(264)  评论(0编辑  收藏  举报

全栈开发工程师 - 一只菜鸟的成长之路

这是一位软件开发工程师的个人站,内容主要是网站开发方面的技术文章,大部分来自学习或工作,部分来源于网络,希望对大家有所帮助。

联系我:2351180282@qq.com