Ruby中的数组
Ruby数组
注意,Array类中包含了Enumerable模块,所以Enumerable中的方法也都能使用,例如Enumerable中非常好用的reduce()方法。
创建数组
字面常量创建
1 |
# 1.使用[xxx]方式创建 |
创建arr时,允许解析表达式。例如,范围表达式、变量等。
1 |
[-10...0, 0..10] |
其中上面表示Range的
-5...0
和
0..5
区别在于前者不包含尾边界,即它是左闭右开的,而后者是左右都闭。
Array.new()创建
Array类的new()方法可以创建数组。这样可以一次性划分好数组的内存空间,一定程度上避免了后续数组自动扩容时引起的内存空间划分开销。
1 |
ary = Array.new #=> [] |
当new()指定了第二个参数时,各初始化的元素将 指向同一个对象 。
1 |
arr=Array.new(3, "abc") |
例如,初始化互不影响的3个『abc』元素数组。
1 |
arr = Array.new(3) {"abc"} |
多维数组:
1 |
Array.new(3){Array.new(2)} #=> [[nil, nil], [nil, nil], [nil, nil]] |
Array()创建
通过Kernel模块提供的
Array(arg)
方法创建数组。这实际上是将对象转换成Array对象,它的内部逻辑如下:
1 |
{:a => "a", :b => "b"}.to_a #=> [[:a, "a"], [:b, "b"]] |
关于它的内部逻辑,稍做演示。例如,String类既没有to_ary,也没有to_a方法:
1 |
"a".methods.grep(/to_a|to_ary/) #=> [] |
to_a()创建
如果某个类定义了to_a()方法,可以将对象转换成数组结构。例如,hash类有to_a()方法,所以可以将hash结构转换成数组。
1 |
my_hash = {Perl: "Larry Wall", Ruby: "Matz"} |
split()从字符串创建
字符串对象有split()方法,可以按照指定的分隔符将字符串切割成数组。
1 |
p "perl python ruby".split # ["perl", "python", "ruby"] |
访问数组元素
索引/slice取元素
获取数组的方式有多种。列出了以下几种常见的(返回多个元素的方式将以新数组的方式返回):
1..
或
1...
或
1..nil
等均表示无限,即取index=1开始的所有数组元素
由于
arr[]
提供的功能已经足够丰富,所以at()方法和slice()方法很少用。
如果索引越界,则返回nil。但是有一个特殊情况,当使用slice的操作时,因为要返回的是数组,所以有如下”异常”情况:
1 |
a = %w(a b c d e) |
上面a[5]返回nil,但a[5,x]返回空数组,而a[6]以及a[6,x]则直接返回nil。首先a[6]以及a[6,x]都是索引越界,所以返回nil。而a[5]的5是索引越界,所以返回nil。问题是a[5,x]返回的是空数组,而不是nil。这是因为a[5,x]是一个slice操作,它要求返回新数组。从下面的slice操作可以理解为什么a[5,1]返回空数组。
1 |
p a[0, 5] # ["a", "b", "c", "d", "e"] |
也就是说,对于slice操作,arr[len, x]的arr[len]可以认为是最边缘的元素,尽管arr[len]已经越界了。
其实
slice()
是按规则删除元素,只不过它不影响原始数组。而
slice!()
则是删除一些元素并直接影响原始数组,也就是在原处修改数组。
1 |
a = [1, 2, 3, 4] |
因为通过索引方式取值时,索引越界时默认会返回nil。因此还定义了另一个方法fetch,它会在索引越界时直接报错或指定默认返回信息:
1 |
arr = [11,22,33,44] |
Array.values_at()取分散元素
Array类提供了values_at()方法,可以取得每个数组对象中指定索引处的多个分散元素。
1 |
a = %w(a b c d e) |
除了这种方式可以取得分散元素,还可以使用map()函数进行操作。例如,等价于
values_at(0,0,2,4,10)
的写法为:
1 |
p [0, 0, 2, 4, 10].map{|i| a[i]} |
其它一些方法
比如,默认情况下索引越界时将返回nil,使用fetch()方法可以获取指定索引的元素,且可以指定越界时的默认值,否则直接报错。
1 |
a=%w(a b c d e) |
使用first()和last()可以取得数组中的第一个元素和最后一个元素,它们会取得元素后立即退出。
1 |
a = %w(a b c d e) |
使用take()可以取得数组中的前n个元素,使用drop()可以取得除了数组前n个元素外剩下的元素。它们都不会删除元素。因为不改变原数组,所以它们的时间复杂度都是O(n),其中n为所返回元素个数。
1 |
a = %w(a b c d e f) |
检索数组
它们返回的都是数组,只不过一些是原地修改,一些是返回新的数组对象。
复杂的三角恋
在array中,filter和select等价,在Enumerable中也有filter、select和find_all,它们等价。
此外,Enumerable中的find是find_all的单元素版,等价于Enumerable的detect。
1 |
arr = [1, 2, 3, 4, 5, 6] |
1 |
arr = [1, 2, 3, 4, 5, 6] |
为数组元素赋值
[] at slice
方法都可以为数组中指定位置处元素赋值。唯一需要注意的是正负数索引位置的区别:
1 |
a = %w(a b c d e) |
赋值语句的返回值是所赋值内容。例如
a[1,0] = %w[B C]
的返回值是
%w[B C]
,于此同时原始数组被修改。
需要注意,当使用数组子集赋值(即使用范围索引
arr[x..y]
或
arr[x,y]
)时,等号右边的内容将展开对各个范围内的元素进行赋值。但如果是对单个元素赋值,则是将等号右边的整个对象赋值给该元素。从数组元素保存的是引用去考虑这方面,很容易理解。
1 |
arr = [1,2,3,4,5] #=> [1, 2, 3, 4, 5] |
扩充数组对象
数组可以执行
+ - * & |
操作,不过
- & |
是将数组作为集合进行操作的,而使用
+
和
*
可以扩展数组。
因为ruby中的一元运算符操作
x += y
和二元运算符操作
x = x + y
完全等价,都会创建新对象x。所以,涉及到大数组时,效率会比较低下。
+
可以将两数组加在一起返回一个新数组:
1 |
arr = [1, 2, 3] |
concat()
方法也能将0到多个数组扩展到一个数组上。它是原地修改的,所以时间复杂度是O(n),n为追加部分的元素个数。注意,concat()会将参数数组全都”压平”然后追加到原数组尾部:
1 |
["a", "b"].concat(["c", "d"]) # ["a", "b", "c", "d"] |
*
对于数组有两种用法:
1 |
# 1.数组乘一个数值对象,返回新数组 |
如果只想分隔除最后一个元素外的其它元素,并单独处理最后一个元素。可参考如下:
1 |
arr = %w[perl php shell ruby] |
数组信息和数组测试
获取数组长度,可以使用count()、length()或size()方法,后两者等价。而count()方法通过参数或语句块的方式还能获取满足条件的元素个数。
1 |
a = %w(a b c d e) |
检查数组是否为空数组,使用
empty?()
方法:
1 |
p a.empty? # false |
检查数组是否包含某元素,使用
include?()
方法:
1 |
p a.include?('c') # true |
向数组中增、删元素
插入元素
push()/append()、unshift()/prepend()、insert()或特殊符号
<<
,注意它们都是原处修改数组对象的:
1 |
# push()向尾部插入元素 |
对于大数组(或未来可能会变大的数组)来说,不要使用insert和unshift插入元素,因为它们会伴随着元素的迁移。而应该使用
<<
或
push
或
append
在尾部插入元素。
删除元素
pop()、shift()、delete_at():
1 |
# pop()移除数组尾部元素并返回该元素 |
对于大数组(或未来可能会变大的数组)来说,不要使用delete_at和shift删除元素,因为它们会伴随着元素的迁移。而应该使用pop删除尾部元素。
按给定值删除元素
delete():找到元素则删除,找不到元素则返回nil,如果给了语句块,则在且仅在找不到元素时执行语句块
1 |
# delete()删除数组中等于某个值的元素 |
删除重复元素
uniq()、compact()以及成对的带感叹号后缀的uniq!()、compact!():
1 |
# uniq()删除重复元素,不是原处修改对象的 |
1 |
# compact()删除所有nil元素,不是原处修改对象 |
clear()直接清空数组:
1 |
a = [1,2,3] |
clear()的内部行为是将数组的start元素索引和元素数量total设置为0。
直接将空数组赋值给变量
arr=[]
也可以清空数组,但其内部过程是创建一个空数组对象并让变量指向该空数组。
迭代数组
此处仅简单介绍for/while/times迭代和数组自身几个迭代方法,关于从Enumerator获取的相关的迭代方式,参见对应文章: Enumerator各种迭代方法 。
数组自身迭代方法 :
for、while、times迭代
1 |
# for |
each()
1 |
each {|item| block} → ary |
迭代数组中每个元素,并将每个元素传递给语句块中的变量。最后返回数组自身。
1 |
arr = [1, 2, 3, 4, 5] |
注意,Ruby中是按引用赋值的,所以each迭代(也包括for,for的本质是each)时控制变量总是引用数组原元素,也因此,修改控制变量的值会直接修改原数组元素,除非这个元素是不可变对象。
例如,上面示例中数组的元素全部是数值,数值是不可变对象,所以修改控制变量a后不会影响原数组元素。但如果元素是可变对象,则会直接修改原数据:
1 |
a=%w[perl shell python php ruby] |
each_index()
1 |
each_index {|index| block} → ary |
迭代数组中的每个元素,并将每个元素的索引传递给语句块中的变量。最后返回数组自身。
1 |
a = ["a", "b", "c", "d"] |
reverse_each()
1 |
# reverse_each反序迭代数组 |
数组转换和测试
1 |
# try_convert(arg)将转换成数组 |
1 |
# to_a()和to_ary(),都返回self |
1 |
# to_s()等价于inspect() |
1 |
# to_h()将数组转换成hash |
数组大小、等同的比较
四种比较方式:等值比较
==
、hash值比较
eql?
、大小比较
<=>
和是否同一数组对象的比较
equal?()
。
数组还继承了Object类的
===
,对于数组而言,它等价于
==
。
==
只有两数组长度相同、两数组对应索引位置的元素使用
==
测试也相等,才判定两数组相等。
返回true/false。注意,如果含有nil元素,则nil与nil的比较结果为true。
1 |
[ "a", "c" ] == [ "a", "c", 7 ] # false |
eql?()
只有两数组对象的内容完全相同时返回true,否则返回false。
它是根据各对象计算出的hash值比较的,一般来说比
==
要严格一点。
1 |
[1, 2] == [1, 2.0] # true |
equal?()
继承自Object类,它比较的是两者是否是同一对象。这个方法基本上不会被子类重写。
这个方法比
==
和
eql?()
都要严格。
1 |
[1,2].equal?([1,2]) # false |
<=>
对于数组而言,对数组中的每个元素都一一对应的比较,直到找到某个数组中的元素不等于另一个数组中对应索引处的元素就返回结果。如果数组长短不一致,且较短数组的所有元素都和另一数组对应元素相等,则长数组更大。于是可以推断,只有两数组长度、内容完全相等(通过
==
号比较,例如1等于1.0),数组才相等。
1 |
arr = [1, 3, 5, 7] |
数组元素排序
数组Mix-in了Enumerable模块,它具备
sort
和
sort_by
两个方法,且还具备对应的原地修改的排序方法。