组和哈希,下面我们来实现点唱机的 SongList 类.我们先列出在 SongList 中所需
的基 本的方法的一个列表,我们
希望随着我们的进度不断丰富它,现在先把它做出来.
append( aSong ) >> list 向列表中添加指定的歌曲
deleteFirst() >> aSong
从
列表中删除第一首歌曲并返回该歌曲
deleteLast() >> aSong 从列表中删除最后一首歌并返回该歌曲
[anIndex] >> aSong 从列表中返回 anIndex 所索引的歌曲,可以是整数索引或者歌曲的标题. (译者注:这里要实现
一个操作符方法即[]方法)
这个列表给我们一些实现方法的提示.在列表尾端添加歌曲的功能,在最前和
最后位置删除歌曲的功能.建 议使用双列----一个两端队列----这样我们可以使用 Array 来实现,同样,数组也支持用一个整数来索引歌曲.
但是我们也需要使用歌曲标题来索引歌曲,可能会想到使用哈希,那样用标题做键歌曲做值.那么可以使用 哈希吗?也许可以,不过这样有
问题.首先哈希是无序的,所以我们不得不使用一个
辅助的数组来跟踪列
表. 一个更大的麻烦是哈希不支持多个键对应一个值,这会给我们的播放列表带来麻烦,因为一首歌可能会被播 放许多次.我们可以在一个歌曲的数组中搜索
需要的歌曲标题,如果这会成为执行上的瓶颈,那么
我们会在 后面
加入一些基于哈希的查找特性.
我们从一个
基本的 initialize 方法开始我们的类,
创建一个数组用来存放歌曲和一个引用它
的实例变量 @songs.
class SongList def initialize @songs=Array.new
end end
SongList#append 方法在@songs 数组末尾添加歌曲,返回它
自己也就是当前的 SongList 对象.这是一个有 用的特性,可以让我们把多个 append 调用联接在一起,后面会看到这个例子.
class SongList def append(aSong) @songs.push(
aSong) self end end
然后添加 deleteFirst 和 deleteLast 方法,
简单地用 Array#shift 和 Array#pop 来分别实现.
class SongList def deleteFirst @songs.shift end def
deleteLast @songs.pop end
end
让我们来快速地测试一下,在列表中添加四首歌曲.炫耀一下,我们用 append 返回的 SongList 对象来联接这 些
方法调用.
list = SongList.new list. append(Song.new('title1', 'artist1', 1)). append(Song.new('title2', 'artist2', 2)). append(Song.new('title3', 'artist3', 3)). append(Song.new('title4', 'artist4', 4))
然后检查一下列表
的开始和结束位置
是否正确,当列表空的时候返回 nil.
list.deleteFirst list.deleteFirst list.deleteLast list.deleteLast list.deleteLast
>> >> >> >> >>
Song: title1--artist1 (1) Song: title2--artist2 (2) Song: title4--artist4 (4) Song: title3--artist3 (3) nil
很好,下一个方法是[],通过索引来
访问元素.如果索引是整数(在这里我们用 Object#kind of?来
检查),那么
返回该位置的元素.
class SongList def [](key) if key.kind_of?(Integer) @songs[key] else # ... end end end
再来测试一下
list[0] list[2] list[9]
>> >> >>
Song: title1--artist1 (1) Song: title3--artist3 (3) nil
现在需要添加通过歌曲标题来索引的功能,这要求扫描整个歌曲列表,检查每一首歌曲标题.在这之前,我们 需要先来熟悉一下 Ruby 最简洁的一个
特性:迭代器.
代码块和迭代器 码块和迭代器
(译者注:
关于代码块,Ruby 的
作者在
2003 年 9 月的访谈中提及到,参看注 3)
下一
个问题是实现 SongList 的[]方法,它用一个字符串来
搜索一首歌曲的标题,看起来很简单:我们有一个歌 曲的列表,遍历整个列表依次匹配每一首歌曲的标题即可.
class SongList def [](key) if key.kind_of?(
Integer) return @songs[key] else for i in 0...@songs.length return @songs[i] if key == @songs[i].name end end return nil end end
它能工作,并且看上去也很熟悉,一个 for 循环遍历整个数组,能
不能做的更自然些呢?
事实上有更
自然的方法.这里我们的 for
循环要求数组的一些私有信息,它要求数组的长度,然后按序匹配每
一个值.为什
么不要求数组仅提供一个对其每个元素的检测呢?这正是 Array 的 find 方法所做的.
class SongList def [](key) if key.kind_of?(
Integer) result = @songs[key] else result = @songs.find { |aSong| key == aSong.name } end return result end end
我们还可以把 if 用作语句修饰符来缩短句子.
class SongList def [](key) return @songs[key] if key.kind_of?(Integer) return @songs.find { |aSong|