Ruby操纵数据结构(一)

所有部分应强制被放置在一起。你必须知道由你组装在一起的只能由你解开。因此,如果你不想再次把它们组在一起,则必须有原因。可以使用所有手段,但你不能使用锤子。

IBM 维护手册(1925)


简单变量不会满足所有程序。每种现代语言都支持很复杂的数据结构形式且都提供创建抽象数据类型的能力。
历史上,数组是最早为人所知,使用范围最广的复杂数据结构。很早以前,在FORTRAN 内,它们被称为下划线变量。今天它们些改变,但在所有语言内它的基本思想是一样的。
近年来,哈希表已变成非常流行的设计工具。像数组,哈希表是一个被索引的数据元素集合。与数组不同,哈希表可以由任意对象索引。( Ruby 中,像大多数语言一样,数组元素是由数字索引来访问的。)
最后,在本章,我们将考查更高级的数据结构。有些只是数组或哈希表的视图;例如,堆栈和队列可以很容易地用数组实现。其它结构如树,图可能根据不同的情况及程序员的偏好来决定如何实现。
但是,现在我们不走的太远了。我们将从数组开始。



一、用数组工作
Ruby 中,数组是由整数索引的,它以零为基数,就像C 语言的数组。但它们之间的共同之处就这些。
Ruby 数组是动态的。在你创建数组时,可以( 但不是必须) 指定它的大小。创建之后,它可以按需求动态增长而不需程序员过多干涉。
Ruby 数组让人感觉有些异类,它可以同时存储多种数据类型而不只是一种。实际上,它存储的是对象引用而不是对象本身,不是具体的值如Fixnum 值。
数组持有它自己的长度,所以我们不必浪费时间来计算它或为了与数组同步而将其保存在额外的变量中。同样,迭代器也被定义了,所以无论如何我们都不需要知道数组的长度。
最后,Ruby 内的类Array 为访问数组提供了很多实用的功能,搜索,连接,以及数组的其它操作。我们将花费余下章节探讨它的内置功能及如何扩展它。


1 、创建与初始化数组
特殊的类方法[ ] 被用于创建数组( 译注:它的别一个用法是索引数组,在后面讨论。) ;被列在圆括号内的数据元素用于填充数组。有三种方式可以调用这个方法,像下面显示的( 注意:数组a,b,c 是等同的)
a = Array.[](1,2,3,4)
b = Array[1,2,3,4]
c = [1,2,3,4]
同样,类方法new 可以接受零,一或两个参数。第一个参数用于指定数组的初始化大小( 元素数量) 。第二个参数是初始化数组内每个元素的值。这儿一个例子:
d = Array.new
# 创建空数组
e = Array.new(3)
# [nil, nil, nil]
f = Array.new(3, "blah")
# ["blah", "blah", "blah"]
译注:文档中是这样写的
格式:

1.
Array.new(size=0, obj=nil)

2.
Array.new(array)

3.
Array.new(size) {|index| block }
在第三种形式中,将使用块的计算结果来设定值。在设定每个数组元素时都会即计算该块,因此可以将所有元素都变成某对象的拷贝。
p Array.new(5) {|i| i }
# => [0, 1, 2, 3, 4]
ary = Array.new(3, "foo")
ary.each {|obj| p obj.id }
# => 537774036

537774036

537774036
ary = Array.new(3) { "foo" }
ary.each {|obj| p obj.id }
# => 537770448

537770436

537770424
第二种形式中,拷贝参数所指的数组并将其返回。
p Array.new([1,2,3]) # => [1,2,3]


2 、数组元素的访问及赋值
元素引用与赋值可以分别使用类方法[ ] [ ]= 来完成。每种类方法都可以接受一个整数参数,或者一个整数对( 包括开始和长度) ,或范围。负整数索引从尾向前计数,它从-1 开始。
同样,特殊的实例方法工作起来就像是一个元素引用。因为它只可以接受一个整数参数,它轻巧快捷。这儿是个例子:
a = [1, 2, 3, 4, 5, 6]
b = a[0]
# 1
c = a.at(0)
# 1
d = a[-2]
# 5
e = a.at(-2)
# 5
f = a[9]
# nil
g = a.at(9)
# nil
h = a[3,3]
# [4, 5, 6]
i = a[2..4]
# [3, 4, 5]
j = a[2...4]
# [3, 4]
a[1] = 8

# [1, 8, 3, 4, 5, 6]
a[1,3] = [10, 20, 30]
# [1, 10, 20, 30, 5, 6]
a[0..3] = [2, 4, 6, 8]
# [2, 4, 6, 8, 5, 6]
a[-1] = 12
# [2, 4, 6, 8, 5, 12]
注意:下面的例子中超出数组大小的引用是如何引起数组增长的( 注意,比子数组可以用比原数组更多的元素来替换,而这也会引起数组增长)
k = [2, 4, 6, 8, 10]
k[1..2] = [3, 3, 3]
# [2, 3, 3, 3, 8, 10]
k[7] = 99
# [2, 3, 3, 3, 8, 10, nil, 99]
最后,我们得提一下,对数组的单独元素进行赋值的话,实际上会做为嵌套元素被插入( 不像赋值给一个范围) ,看下面:
m = [1, 3, 5, 7, 9]
m[2] = [20, 30]
# [1, 3, [20, 30], 7, 9]
# On the other hand...
m = [1, 3, 5, 7, 9]
m[2..2] = [20, 30]
# [1, 3, 20, 30, 7, 9]
slice 方法很简单就像是[ ] 方法的别名:
x = [0, 2, 4, 6, 8, 10, 12]
a = x.slice(2)
# 4
b = x.slice(2,4)

# [4, 6, 8, 10]
c = x.slice(2..4)
# [4, 6, 8]
方法first last 分别返回数组的第一个和最后一个元素。如果数组为空,它们将返回nil 。这儿是一个例子:( 译注:它们也可接受一个参数,用于指出从第一个开始返回几个值。)
x = %w[alpha beta gamma delta epsilon]
a = x.first
# "alpha"
b = x.last
# "epsilon"
# 译注:下面例子后加的。
a = [ "q", "r", "s", "t" ]
a.first
#=> "q"
a.first(1)
#=> ["q"]
a.first(3)
#=> ["q", "r", "s"]
我们已经看到一些元素引用的技术可以返回一个完整的子数组。还有可以访问多个元素的一些方法,现在我们看一下。
方法indices 接受一个索引列表并且返回的数组只包含这些元素。此处不可以使用范围( 因为元素不一定是邻近的) 若指定了超出范围的索引值时,将指派 nil 与其对应。 这个方法的别名是indexes 。这儿是一个例子:( 译注:这个方法已由values_at 代替了。)
x = [10, 20, 30, 40, 50, 60]
y = x.indices(0, 1, 4)
# [10, 20, 50]
z = x.indexes(2, 10, 5, 4)
# [30, nil, 60, 50]
译注:这些方法在文档中的定义
访问:
1.
array[index] → obj or nil
2.
array[start, length] → an_array or nil
3.
array[range] → an_array or nil
4.
array.slice(index) → obj or nil
5.
array.slice(start, length) → an_array or nil
6.
array.slice(range) → an_array or nil
7.
array.first → obj or nil
8.
array.first(n) → an_array
9.
array.last → obj or nil
10.
array.last(n) → an_array
赋值:
1.
array[index] = obj → obj
2.
array[start, length] = obj or an_array or nil → obj or an_array or nil
3.
array[range] = obj or an_array or nil → obj or an_array or nil


3 、获取数组尺寸

方法length( 别名size) 会获取数组内的元素数量。注意:这个值会比最后元素的索引小:
x = ["a", "b", "c", "d"]
a = x.length
# 4
b = x.size

# 4
方法nitems 是一样的,除了它不计数nil 元素( 即返回非nil 元素的数量)
y = [1, 2, nil, nil, 3, 4]
c = y.size
# 6
d = y.length
# 6
e = y.nitems
# 4
译注:这些方法在文档中的定义
1.
array.length → int
2.
array.nitems → int




4 、数组比较

比较数组是需要诀窍的。如果你正在做,你应该小心。
实例方法<=> 用于比较数组。它的工作与上下文环境有关,返回值可是-1( 小于) 0( 等于) ,也可是1( 大于) 。方法== != 依赖于这个方法的实现。
数组以一种" 元素法则" 风格来比较;首先,两个元素不等,则推定整个比较不等。 ( 所以,最左边的元素优先,正好像按"by eye" 风格比较两个长整数时,每次只能查看一个数字。) 这儿是个例子:
a = [1, 2, 3, 9, 9]
b = [1, 2, 4, 1, 1]
c = a <=> b
# -1 (meaning a < b)
如果所有元素相等,则数组相等。如果一个数组比另一个大,且在较小的数组长度内它们相等,则认为长的数组大。( 译注: 如果各个元素均相等,且其中一个数组已到达尾部时,则认定较短的数组为小。)
d = [1, 2, 3]
e = [1, 2, 3, 4]
f = [1, 2, 3]
if d == f

puts "d equals f"
# Prints "d equals f"
end
因为Array 类没有混插Comparable 模块,通常的操作< > <= ,和>= 没有在数组中定义。但如果你需要的话,可以很容易地定义它们:
class Array

def <=> (other)



(self <=> other)== -1

end

def <=(other)



(self < other) or (self == other)

end

def >(other)



(self <=> other) == 1

end

def >=(other)



(self > other) or (self == other)

end
end
定义之后,就可以像你想的那样使用它们了:
if a < b

print "a < b"
# Prints "a < b"
else

print "a >= b"
end
if d < e

puts "d < e"
# Prints "d < e"
end
可以想像得到若两个元素没有<=> 的定义或无意义的话( 译注:指元素之间没有必然联系。) ,比较的数组会返回什么结果。结果是运行时错误(TypeError) ,因为3<=>"x" 的比较是有问题的:
g = [1, 2, 3]
h = [1, 2, "x"]
if g < h
# Error!

puts "g < h"
# No output
end
然而,在这种情况下你会对相等及不相等还能工作感到困惑的话。是因为两个不同类型对象被自然地认为不相等,甚至不用我们说谁大于或小于谁:
if g != h
# No problem here.

puts "g != h"
# Prints "g != h"
end
最后,两个包含不同数据类型的数组仍然可以用< > 操作进行比较,这仍是不可思议的。这种情况下,此处显示,我们仍能获得结果( 译注:在1.8 中不能工作)
i = [1, 2, 3]
j = [1, 2, 3, "x"]
if i < j
# No problem here.

puts "i < j"
# Prints "i < j"
end


5 、数据排序

使用内置的sort 方法可以很容易地为数组排序,如下面显示:
words = %w(the quick brown fox)
list = words.sort
# ["brown", "fox", "quick", "the"]
# 或使用有破坏性的 sort 用于替换。
words.sort!
# ["brown", "fox", "quick", "the"]
这个方法假设数组内的所有元素彼此之间都是可比较的。混合数组,如[1, 2, "three", 4] ,通常会给出一个类型错误。
在这种情况,你可以给方法附加块后来调用它。这儿的例子假设对每个元素都使用to_s 方法( 将它们转换成字符串)
a = [1, 2, "three", "four", 5, 6]
b = a.sort { |x,y| x.to_s <=> y.to_s}
# b 现在是 [1, 2, 5, 6, "four", "three"]
当然,像这样的次序( 这种情况,依赖于ASCII) 可能是无意义的。如果你有这样一个不同类型的数组,你可以按你自己的求进行排序( 如按对象的不同类型等等)
这个技术可以工作,因为块在每次调用后返回一个整数(-1,0 1) 。当返回-1 时,意味着x 小于y ,两个元素被交换。所以按降序排序,我们可以简单地交换比较的次序,像这样:
x = [1, 4, 3, 5, 2]
y = x.sort { |a,b| b 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值