Ruby-容器

来自站长百科
跳转至: 导航、​ 搜索

导航: 上一页 | ASP | PHP | JSP | HTML | CSS | XHTML | aJAX | Ruby | JAVA | XML | Python | ColdFusion

容器[ ]

在实现以前,我们需要决定怎样在SongList对象中储存歌曲列表,有三种方法,使用Ruby的Array类型或者Hash类型,要不然就自己做一个列表结构。偷懒的结果就是先来看看数组和哈希,从中选择一个用在我们的类中。


数组[ ]

Array类保存着对象引用的集合。每一个对象的引用在数组中都有一个位置,通过一个非负整数来索引。

你可以使用字面值或者直接生成一个Array对象,一个字面上的数组是被方括号括住的一个对象序列。

a = [ 3.14159, "pie", 99 ]
a.type >> Array
a.length >> 3
a[0] >> 3.14159 v a[1] >> "pie"
a[2] >> 99
a[3] >> nil
b = Array.new
b.type >> Array
b.length >> 0
b[0] = "second"
b[1] = "array"
b >> ["second", "array"]


数组通过[]操作符来索引,就像大多数的Ruby操作符,这实际上也是一个方法(在Array类中)所以也可以在子类中重载,如例中所示数组索引从0开始。使用一个single整数来索引数组,返回该位置的对象,如果在那个位置没有对象则返回nil。如果使用一个负整数索引数组,那么它从数组尾端返回,参看35页的表4.1。

a = [ 1, 3, 5, 7, 9 ]  
a[-1] >> 9
a[-2] >> 7
a[-99] >> nil


也可以使用一对数字来索引数组,[start,count]。这会返回一个新数组,它由从start开始的count个对象的引用组成。

a = [ 1, 3, 5, 7, 9 ]  
a[1, 3] >> [3, 5, 7]
a[3, 1] >> [7]
a[-3, 2] >> [5, 7]


(译者注:注意负整数索引的方向依然是从前向后)

最后你也可以使用区间来索引数组,开始和结束位置之间插入两个或者三个点,两个点的形式表示包含结束位置,三个点不包含。

a = [ 1, 3, 5, 7, 9 ]  
a[1..3] >> [3, 5, 7]
a[1...3] >> [3, 5]
a[3..3] >> [7]
a[-3..-1] >> [5, 7, 9]


[]操作符对应的有[]=操作符,通过它可以设置数组元素的值。用一个single整数索引,把操作符右边的值赋给该位置的元素。中间所产生的空隙用nil来填补。

a = [ 1, 3, 5, 7, 9 ]           >>      [1, 3, 5, 7, 9] 
a[1] = 'bat' >> [1, "bat", 5, 7, 9]
a[-3] = 'cat' >> [1, "bat", "cat", 7, 9]
a[3] = [ 9, 8 ] >> [1, "bat", "cat", [9, 8], 9]
a[6] = 99 >> [1, "bat", "cat", [9, 8], 9, nil, 99]


如果[]=的索引有两个数(开始和长度)或者是一个区间,那么在原始数组中的对应元素就会被操作符右边的值代替;如果索引的长度为0,那么右边的值就插入到开始位置前面,不删除元素;如果右边也是一个数组,它的元素代替原始数组的元素;如果索引所选择的元素数目和右边的不一样,那么就自动调整数组的大小来适应。
(译者注:值得一提的是如果出现了空隙,依旧用nil来填补)

a = [ 1, 3, 5, 7, 9 ]         >>      [1, 3, 5, 7, 9] 
a[2, 2] = 'cat' >> [1, 3, "cat", 9]
a[2, 0] = 'dog' >> [1, 3, "dog", "cat", 9]
a[1, 1] = [ 9, 8, 7 ] >> [1, 9, 8, 7, "dog", "cat", 9]
a[0..3] = [] >> ["dog", "cat", 9]
a[5] = 99 >> ["dog", "cat", 9, nil, nil, 99]


数组有大量有用的方法,通过它们你可以把数阻当成堆、栈、集、队列、双列、先入先出列等。278页有完整的数组方法列表。


哈希[ ]

哈希(有时被认为是数组和字典的结合)和数组一样是用来储存对象引用的集合。

不过,区别于通过整数来索引数组,你可以通过任意类型的对象来索引哈希:字符、正则表达式等。在哈希中保存元素实际上是保存了两个对象----键和值。用键可以索引到对应的值。哈希中的值可以是任意类型的对象,下面的例子使用了哈希字面值:括号括起来的键值对。

h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }  
h.length >> 3
h['dog'] >> "canine"
h['cow'] = 'bovine'
h[12] = 'dodecine'
h['cat'] = 99
h >> {"donkey"=>"asinine", "cow"=>"bovine", "dog"=>"canine", 12=>"dodecine", "cat"=>99}


对比于数组,哈希有一个显见的好处就是可以使用任意对象做索引,但同时一个显见的不好处就是它的元素是无序的,所以你不能简单地把哈希用作堆栈或者队列。

你很快会发现哈希是ruby中使用最广泛的一类数据结构。317页有完整的哈希类中实现的方法列表。

实现一个SongList容器

上面简单讨论了一下数组和哈希,下面我们来实现点唱机的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 >> Song: title1--artist1 (1)
list.deleteFirst >> Song: title2--artist2 (2)
list.deleteLast >> Song: title4--artist4 (4)
list.deleteLast >> Song: title3--artist3 (3)
list.deleteLast >> nil


很好,下一个方法是[],通过索引来访问元素。如果索引是整数(在这里我们用Object#kind of?来检查),那么返回该位置的元素。

class SongList
def [](key)
if key.kind_of?(Integer)
@songs[key]
else
# ...
end
end
end


再来测试一下

list[0] >> Song: title1--artist1 (1)
list[2] >> Song: title3--artist3 (3)
list[9] >> nil
现在需要添加通过歌曲标题来索引的功能,这要求扫描整个歌曲列表,检查每一首歌曲标题。在这之前,我们需要先来熟悉一下Ruby最简洁的一个特性:迭代器。