# **第 13 章 數組類**
本章將詳細介紹數組(`Array`)類。
-
**數組的創建方法**
介紹如何新創建數組、以及如何通過其他對象創建數組。
-
**索引的使用方法**
數組的基本用法是利用索引(下標)訪問數組內各元素。在這一部分中我們會介紹索引的相關用法。
-
**作為集合的數組與作為列的數組**
Ruby 中可以把數組作為集合操作,也可以把數組作為列操作。在這一部分中我們會介紹這兩種數組的用法。
-
**主要的數組方法**
通過數組方法,不僅可以將執行結果以新對象的形式返回,還可以對元素進行互換、刪除等操作,進而變更已存在的對象。在這一部分中我們會詳細介紹如何使用這些數組方法。
-
**數組與迭代器**
迭代器經常被用來逐個處理數組中的元素。在這一部分中我們會介紹迭代器的基本用法、以及數組的迭代方法。
-
**處理數組中的元素**
除了迭代器之外,使用其他方法也可以處理數組內的各元素。在這一部分中我們會介紹幾種具有代表性的方法。
-
**同時訪問多個數組**
任何對象都可以作為數組的元素。在這一部分中我們會介紹一些數組使用過程中需要注意的事項。
### **13.1 復習數組**
在第 2 章中我們已經介紹過數組了,現在我們先來復習一下。
數組是帶索引的對象的集合。
數組有以下特征:
-
**可以從數組中獲取某個索引的元素(對象)**
例:`print name[2]`
-
**可以將任意的值(對象)保存到數組的某個索引的元素中**
例:`name[0] = " 野尻 "`
-
**使用迭代器可以逐個取出數組中的元素**
例:`names.each{|name| puts name}`

### **13.2 數組的創建方法**
在第 2 章中,我們介紹了使用 `[]` 來創建數組的方法。
~~~
nums = [1, 2, 3, 4, 5]
strs = ["a", "b", "c", "d"]
~~~
除此以外還有其他的創建方法,接下來就簡要地介紹一下。
### **13.2.1 使用 Array.new**
創建類的實例時使用的 `new` 方法,創建數組時也同樣可以使用。
~~~
a = Array.new
p a #=> []
a = Array.new(5)
p a #=> [nil, nil, nil, nil, nil]
a = Array.new(5, 0)
p a #=> [0, 0, 0, 0, 0]
~~~
`Array` 類的情況下,若 `new` 方法沒有參數,則會創建元素個數為 0 的數組;若 `new` 方法只有 1 個參數,則會創建元素個數為該參數個數,且各元素初始值都為 `nil` 的數組;若 `new` 方法有兩個參數,則第 1 個參數代表元素的個數,第 2 個參數代表元素值的初始值。
當希望創建元素值相同的數組時,建議使用這個方法。
### **13.2.2 使用 %w 與 %i**
創建不包含空白的字符串數組時,可以使用 `%w`。
~~~
lang = %w(Ruby Perl Python Scheme Pike REBOL)
p lang #=> ["Ruby", "Perl", "Python", "Scheme", "Pike",
# "REBOL"]
~~~
雖然給人的感覺只是節省了書寫 `" "` 和 `,` 的時間,但是如果能掌握這種字符串數組的創建方法,就會使程序更加簡潔。此外,Ruby2.0 還提供了創建符號(Symbol)數組的 `%i`。
~~~
lang = %i(Ruby Perl Python Scheme Pike REBOL)
p lang #=> [:Ruby, :Perl, :Python, :Scheme, :Pike, :REBOL]
~~~
在本例中,創建數組時使用了 `()` 將數組元素括了起來,但實際上還可以使用如 `<>`、`||`、`!!`、`@@`、`AA` 這樣的任意字符。
雖然 Ruby 允許我們使用任意字符,但若用一些不常用的字符來創建數組的話,可能就會使程序不便于閱讀。在選擇表示字符串數組元素的字符時,還要注意該字符不能在要創建的字符串中出現,因此建議使用 `()`、`<>`、`||`。
### **13.2.3 使用 to_a 方法**
到現在為止,我們已經介紹了三種傳統的創建數組的方法,下面我們就來看看如何將其他對象轉換為數組。
很多類都定義了 `to_a` 方法,該方法能把該類的對象轉換為數組。
~~~
color_table = {black: "#000000", white: "#FFFFFF"}
p color_table.to_a #=> [[:black, "#000000"],
# [:white, "#FFFFFF"]]
~~~
對散列對象使用 `to_a` 方法,結果就會得到相應的數組的數組。具體來說就是,將散列中的各鍵、值作為一個數組,然后再把這樣的數組放到一個大數組中。
### **13.2.4 使用字符串的 split 方法**
我們再來介紹一個將對象轉換為數組的方法。對用逗號或者空白間隔的字符串使用 `split` 方法,也可以創建數組。
~~~
column = "2013/05/30 22:33 foo.html proxy.example.jp".split()
p column
#=> ["2013/05/30", "22:33", "foo.html", "proxy.example.jp"]
~~~
關于 `split` 方法我們會在第 14.6 節中詳細說明。
### **13.3 索引的使用方法**
在了解了如何創建數組之后,下面我們就來看看如何操作數組。
首先,我們將介紹如何用索引操作數組。用索引操作數組的方法是操作數組的基礎方法。這部分內容也許會和第 2 章的內容有些重復,不過這里我們會重點介紹一些新的內容,以使大家能夠對索引有一個系統的了解。
### **13.3.1 獲取元素**
對數組指定索引值,就可以獲取相應的元素。我們可以逐個獲取元素,也可以一次獲取多個元素。
通過 `[]` 指定索引,獲取元素。`[]` 有以下 3 種用法:
(a)*a* [*n*]
(b)*a* [*n..m*] 或者 *a* [*n...m*]
(c)*a* [*n, len*]
用法(a)是我們在第 2 章中使用過的獲取索引值為 *n* 的元素的方法。例如,通過 `alpha[0]` 獲取數組 `alpha` 的首個元素。這里要注意,數組的索引值是從 0 開始的(圖 13.1)。

**圖 13.1 數組與索引的關系**
索引值為負數時,不是從數組的開頭,而是從數組的末尾開始獲取元素(圖 13.2)。如果指定的索引值大于元素個數,則返回 `nil`。

**圖 13.2 索引值為負數**
用法(b)的 *a* [*n..m*] 表示獲取從 *a* [*n*] 到 *a* [*m*] 的元素,然后用它們創建新數組并返回(圖 13.3)。*a* [*n..m*] 表示獲取從 *a* [*n*] 到 *a* [*m*-1] 的元素,并用它們創建新數組返回。雖然下面的例子中只討論 [*n..m*] 的形式,但能用 [*n..m*] 的地方同樣能用 [*n...m*]。

**圖 13.3 指定索引范圍**
如果 `m` 的值比數組長度大,則返回的結果與指定數組最后一個元素時是一樣的(圖 13.4)。

**圖 13.4 索引值比數組長度大時**
用法(c)[*n, len*] 表示從 *a* [*n*] 開始,獲取之后的 *len* 個元素,用它們創建新數組并返回(圖 13.5)。

**圖 13.5 從某個元素開始,獲取多個元素**
另外,我們還可以用普通的方法代替 `[]`。
-
***a*.`at`(*n*) ……與 *a*[*n*] 等價**
-
***a*.`slice`(*n*) ……與 *a*[*n*] 等價**
-
***a*.`slice`(*n..m*) ……與 *a*[*n..m*] 等價**
-
***a*.`slice`(*n, len*) ……與 *a*[*n, len*] 等價**
不過一般情況下我們很少會使用上述方法。
### **13.3.2 元素賦值**
使用 `[]`、`at`、`slice` 方法除了可以獲取元素外,還可以對元素賦值。
-
***a*[*n*] = *item***
這是將 *a* [*n*] 的元素值變更為 *item*。在下面的例子中,我們來嘗試把 `B` 賦值給第 2 個元素,把 `E` 賦值給第 5 個元素(圖 13.6)。

**圖 13.6 元素賦值**
上面的例子介紹的是對一個元素賦值,實際上 Ruby 還可以一次對多個元素賦值。指定多個元素的方法與在 13.3.1 節中介紹的獲取多個元素的方法是一樣的。
在下面的例子中,我們來嘗試對數組的第 3 個元素到第 5 個元素賦值。圖 13.7 表示的是使用 [*n..m*] 的形式進行賦值時的情況。下面是使用 [*n, len*] 的形式進行賦值的例子。
~~~
alpha = ["a", "b", "c", "d", "e", "f"]
alpha[2, 3] = ["C", "D", "E"]
p alpha #=> ["a", "b", "C", "D", "E", "f"]
~~~

**圖 13.7 對多個元素賦值**
### **13.3.3 插入元素**
我們還可以在保持當前元素不變的情況下,對數組插入新的元素。
插入元素其實也可以被認為是對 0 個元素進行賦值。因此,指定 [*n*, 0] 后,就會在索引值為 *n* 的元素前插入新元素(圖 13.8)。

**圖 13.8 插入元素**
### **13.3.4 通過多個索引創建數組**
通過使用我們在 13.3.1 節中介紹的方法,雖然可以獲取多個連續的元素,但是卻不能獲取分散的元素。而使用 `values_at` 方法,就可以利用多個索引來分散獲取多個元素,并用它們創建新數組。
-
***a*.`values_at` (*n1*, *n2*, …)**
用這個方法,我們就可以每隔一個元素獲取一次(圖 13.9)。

**圖 13.9 獲取分散的元素并創建數組**
### **13.4 作為集合的數組**
到目前為止的數組操作都是通過索引完成的。也就是說,從哪里獲取元素、給哪個元素賦值、在哪里插入元素這些操作都是直接指定數組索引后進行的。
的確,數組、`Array` 類本來就是帶有索引的對象,使用索引也是理所當然。不過有些時候我們會希望不通過索引而直接操作數組元素。
例如,我們可以把數組當成集合,這樣一來,`Array` 類中的各元素就變了集合里的元素。
然而,由于集合沒有順序的概念,因此 `["a", "b", "c"]`、`["b", "c", "a"]`、`["c", "b", "a"]` 就都可以被認為是同一個集合。
這樣操作數組時,如果我們還關心“這個對象是數組的第幾個元素”之類的問題,就可能會造成混亂。這是因為,索引操作實際上只是數組封裝的一個功能而已。
接下來,我們就來看看如何把數組當作集合使用。而在下一節中,我們會再討論把數組當作列使用時的方法。
集合的基本運算分為交集和并集。
-
**取出同時屬于兩個集合的元素,并創建新的集合**
-
**取出兩個集合中的所有元素,并創建新的集合**
我們把第 1 種集合稱為交集,第 2 種集合成為并集。
-
**交集**……*ary* = *ary1* & *ary2*
-
**并集**……*ary* = *ary1* | *ary2*
圖 13.10 描述的是 Ruby 數組中的交集和并集。

**圖 13.10 交集與并集**
集合還有另外一種運算——補集,即獲取某個集合中不屬于另外一個集合的元素。但是 `Array` 類的情況下,由于沒有全集的概念,因此也就沒有補集。不過 `Array` 類有把某個集合中屬于另外一個集合的元素刪除的差運算(圖 13.11)。
- **集合的差**……*ary* = *ary1* - *ary2*

**圖 13.11 集合的差**
由于圖 13.11 的數組 `ary2` 中包含的字符串 `"d"` 在數組 `ary1` 中并沒有,因此不會被保留在執行結果中。
### **“|”與“+”的不同點**
連接數組除了可以使用 `|` 外還可以使用 `+`。這兩種方法看起來比較相似,但是有相同元素時它們的效果就不一樣了。
~~~
num = [1, 2,3]
even = [2, 4, 6]
p (num + even) #=> [1, 2, 3, 2, 4, 6]
p (num | even) #=> [1, 2, 3, 4, 6]
~~~
數組 `num` 與數組 `even` 都有元素 `2`。使用 `+` 時元素 `2` 會有兩個,使用 `|` 時相同的元素只會有一個。
### **13.5 作為列的數組**
下面,我們來看看把數組對象當作列來看待時的情況。
數據結構的隊列(queue)和棧(stack)都是典型的列結構。這兩個相對的數據結構都有以下兩種操作數據的方式。
-
**追加元素**
-
**獲取元素**
隊列是一種按元素被追加時的順序來獲取元素的數據結構(圖 13.12(a))。這樣的做法稱為 FIFO(First-in First-out),也就是“先進先出”的意思。這與人們為等待某件事而排成一列時的情況一樣,因此有時候也稱為等待隊列。
而棧則是一種按照與元素被追加時的順序相反的順序來獲取元素的數據結構。這樣的做法稱為 LIFO(Last-in First-out),是一種“先進后出”的數據結構(圖 13.12(b))。也就是說,在末尾追加元素,并從末尾開始獲取元素。

**圖 13.12 隊列與棧**
簡單地說就是,按 A、B、C 的順序保存數據時,按照 A、B、C 的順序取得數據的數據結構就是隊列,按照 C、B、A 的順序取得數據的數據結構就是棧。
隊列與棧都是比較復雜的數據結構,同時也是提高程序運行效率所不可欠缺的工具。
在數組的開頭或末尾插入元素,或者從數組的開頭或末尾獲取元素等操作,是實現隊列、棧等數據結構所必須的前提條件。Ruby 的數組封裝了如表 13.1 所示的方法,因此可以很輕松地實現這些前提條件。
**表 13.1 操作數組開頭與末尾的元素的方法**
<table border="1" data-line-num="288 289 290 291 292 293" width="90%"><thead><tr><th> <p class="表頭單元格">?</p> </th> <th> <p class="表頭單元格">對數組開頭的元素的操作</p> </th> <th> <p class="表頭單元格">對數組末尾的元素的操作</p> </th> </tr></thead><tbody><tr><td> <p class="表格單元格">追加元素</p> </td> <td> <p class="表格單元格"><code>unshift</code></p> </td> <td> <p class="表格單元格"><code>push</code></p> </td> </tr><tr><td> <p class="表格單元格">刪除元素</p> </td> <td> <p class="表格單元格"><code>shift</code></p> </td> <td> <p class="表格單元格"><code>pop</code></p> </td> </tr><tr><td> <p class="表格單元格">引用元素</p> </td> <td> <p class="表格單元格"><code>first</code></p> </td> <td> <p class="表格單元格"><code>last</code></p> </td> </tr></tbody></table>
利用圖 13.13 所示的 `push` 方法和 `shift` 方法可以實現隊列,利用圖 13.14 所示的 `push` 方法和 `pop` 方法可以實現棧。

**圖 13.13 隊列**

**圖 13.14 棧**
要注意的是,`shift` 方法和 `pop` 方法不只是獲取數組元素,而且還會把該元素從數組中刪除。如果只是單純地希望引用元素,則應該使用 `first` 方法和 `last` 方法。
~~~
a = [1, 2, 3, 4, 5]
p a.first #=> 1
p a.last #=> 5
p a #=> [1, 2, 3, 4, 5]
~~~
### **13.6 主要的數組方法**
數組方法有很多,下面我們將選取最常用的幾種方法,并把具有相同功能的方法歸納在一起來分別加以介紹。
### **13.6.1 為數組添加元素**
-
***a*.`unshift` (*item*)**
將 *item* 元素添加到數組的開頭。
~~~
a = [1, 2, 3, 4, 5]
a.unshift(0)
p a #=> [0, 1, 2, 3, 4, 5]
~~~
-
***a* << *item*
*a*.`push` (*item*)**
`<<` 與 `push` 是等價的方法,在數組 a 的末尾添加新元素 *item*。
~~~
a = [1, 2, 3, 4, 5]
a << 6
p a #=> [1, 2, 3, 4, 5, 6]
~~~
-
***a*.`concat` (*b*)
*a* + *b***
連接數組 *a* 和數組 *b*。`concat` 是具有破壞性的方法,而 `+` 則會根據原來的數組元素創建新的數組。
~~~
a = [1, 2, 3, 4, 5]
a.concat([8, 9])
p a #=> [1, 2, 3, 4, 5, 8, 9]
~~~
-
***a* [*n*] = *item*
*a* [*n..m*] = *item*
*a* [*n, len*] = *item***
把數組 *a* 指定的部分的元素替換為 *item*。
~~~
a = [1, 2, 3, 4, 5, 6, 7, 8]
a[2..4] = 0
p a #=> [1, 2, 0, 6, 7, 8]
a[1, 3] = 9
p a #=> [1, 9, 7, 8]
~~~
> **專欄**
> **具有破壞性的方法**
> 像 `pop` 方法、`shift` 方法那樣,會改變接收者對象值的方法稱為具有破壞性的方法。在使用具有破壞性的方法時需要特別小心,因為當有變量也引用了接收者對象時,如果接受者對象值發生了改變,變量值也會隨之發生變化。我們來看看下面的例子。
~~~
a = [1, 2, 3, 4]
b = a
p b.pop #=> 4
p b #=> [1, 2, 3]
p a #=> [1, 2, 3]
~~~
> 執行 `pop` 方法刪除元素后,變量 `a` 引用的數組的元素也被刪除,從 `[1, 2, 3, 4]` 變為了 `[1, 2, 3]`,同時變量 `b` 引用的數組元素也被刪除了。這是由于執行 `b = a` 后,并不是將變量 `a` 的內容復制給了變量 `b`,而是讓變量 `b` 和變量 `a` 同時引用了一個對象。
> 
> 在 Ruby 的方法中,有像 `sort` 和 `sort!` 這樣,在相同方法名后加上 `!` 的方法。為了區分方法是否具有破壞性,在具有破壞性的方法末尾添加 ! 這一做法目前已經成為了通用的規則。
### **13.6.2 從數組中刪除元素**
根據某些條件從數組中刪除元素。
-
***a*.`compact`
*a*.`compact!`**
從數組 *a* 中刪除所有 `nil` 元素。`compact` 方法會返回新的數組,`compact!` 則直接替換原來的數組。`compact!` 方法返回的是刪除 `nil` 元素后的 `a`,但是如果什么都沒有刪除的話就會返回 `nil`。
~~~
a = [1, nil, 3, nil, nil]
a.compact!
p a #=> [1, 3]
~~~
-
***a*.`delete`(*x*)**
從數組 *a* 中刪除 *x* 元素。
~~~
a = [1, 2, 3, 2, 1]
a.delete(2)
p a #=> [1, 3, 1]
~~~
-
***a*.`delete_at`(*n*)**
從數組中刪除 *a*[*n*] 元素。
~~~
a = [1, 2, 3, 4, 5]
a.delete_at(2)
p a #=> [1, 2, 4, 5]
~~~
-
***a*.`delete_if`{|*item*| … }
*a*.`reject`{|*item*| … }
*a*.`reject!`{|*item*| … }**
判斷數組 *a* 中的各元素 *item*,如果塊的執行結果為真,則從數組 *a* 中刪除 *item*。`delete_if` 和 `reject!` 方法都是具有破壞性的方法。
~~~
a = [1, 2, 3, 4, 5]
a.delete_if{|i| i > 3}
p a #=> [1, 2, 3]
~~~
-
***a*.`slice!`(*n*)
*a*.`slice!`(*n..m*)
*a*.`slice!`(*n, len*)**
刪除數組 *a* 中指定的部分,并返回刪除部分的值。`slice!` 是具有破壞性的方法。
~~~
a = [1, 2, 3, 4, 5]
p a.slice!(1, 2) #=> [2, 3]
p a #=> [1, 4, 5]
~~~
-
***a*.`uniq`
*a*.`uniq!`**
刪除數組 *a* 中重復的元素。`uniq!` 是具有破壞性的方法。
~~~
a = [1, 2, 3, 4, 3, 2, 1]
a.uniq!
p a #=> [1, 2, 3, 4]
~~~
-
***a*.`shift`**
刪除數組 *a* 開頭的元素,并返回刪除的值。
~~~
a = [1, 2, 3, 4, 5]
a.shift #=> 1
p a #=> [2, 3, 4, 5]
~~~
-
***a*.`pop`**
刪除數組 *a* 末尾的元素,并返回刪除的值。
~~~
a = [1, 2, 3, 4, 5]
a.pop #=> 5
p a #=> [1, 2, 3, 4]
~~~
### **13.6.3 替換數組元素**
將數組元素替換為別的元素的方法中,也分為帶 `!` 的和不帶 `!` 的方法,前者是具有破壞性的會改變接收者對象值的方法,后者則是直接返回新數組的方法。
-
***a*.`collect`{|*item*| … }
*a*.`collect!`{|*item*| … }
*a*.`map`{|*item*| … }
*a*.`map!`{|*item*| … }**
將數組 *a* 的各元素 *item* 傳給塊,并用塊處理過的結果創建新的數組。從結果來看,數組的元素個數雖然不變,但由于經過了塊處理,因此數組的元素和之前會不一樣。
~~~
a = [1, 2, 3, 4, 5]
a.collect!{|item| item * 2}
p a #=> [2, 4, 6, 8, 10]
~~~
-
***a*.`fill`(*value*)
*a*.`fill`(*value, begin*)
*a*.`fill`(*value, begin, len*)
*a*.`fill`(*value, n..m*)**
將數組 *a* 的元素替換為 *value*。參數為一個時,數組 *a* 的所有元素值都會變為 *value*。參數為兩個時,從 *begin* 到數組末尾的元素值都會變為 *value*。參數為三個時,從 *begin* 開始 *len* 個元素的值會變為 *value*。另外,當第 2 個參數指定為 [*n..m*] 時,則指定范圍內的元素值都會變為 *value*。
~~~
p [1, 2, 3, 4, 5].fill(0) #=> [0, 0, 0, 0 ,0]
p [1, 2, 3, 4, 5].fill(0, 2) #=> [1, 2, 0, 0, 0]
p [1, 2, 3, 4, 5].fill(0, 2, 2) #=> [1, 2, 0, 0, 5]
p [1, 2, 3, 4, 5].fill(0, 2..3) #=> [1, 2, 0, 0, 5]
~~~
-
***a*.`flatten`
*a*.`flatten!`**
平坦化數組 *a*。所謂平坦化是指展開嵌套數組,使嵌套數組變為一個大數組。
~~~
a = [1, [2, [3]], [4], 5]
a.flatten!
p a #=> [1, 2, 3, 4, 5]
~~~
-
***a*.`reverse`
*a*.`reverse!`**
反轉數組 *a* 的元素順序。
~~~
a = [1, 2, 3, 4, 5]
a.reverse!
p a #=> [5, 4, 3, 2, 1]
~~~
-
***a*.`sort`
*a*.`sort!`
*a*.`sort`{|*i, j*| … }
*a*.`sort!`{|*i, j*| … }**
對數組 *a* 進行排序。排序方法可以由塊指定。沒有塊時,使用 `<=>` 運算符比較。
~~~
a = [2, 4, 3, 5, 1]
a.sort!
p a #=> [1, 2, 3, 4, 5]
~~~
關于如何使用塊指定排序方法,在 11.2.3 節中我們已經介紹過了。
-
***a*.`sort_by`{|*i*| … }**
對數組 *a* 進行排序。根據塊的運行結果對數組的所有元素進行排序。
~~~
a = [2, 4, 3, 5, 1]
p a.sort_by{|i| -i } #=> [5, 4, 3, 2, 1]
~~~
詳情請參考 11.2.3 節。
### **13.7 數組與迭代器**
迭代器是實現循環處理的方法,而數組則是多個對象的集合。在對這些對象進行某種處理,或者取出某幾個對象時,都需要大量用到迭代器。
例如,對數組的各元素進行相同的操作時使用的 `each` 方法就是典型的迭代器。該方法會遍歷數組的所有元素,并對其進行特定的處理。
此外,接收者不是數組的情況下,為了讓迭代器的執行結果能作為某個對象返回,也會用到數組。其中 `collect` 方法就是一個具有代表性的方法。`collect` 方法會收集某種處理的結果,并將其合并為一個數組后返回。
~~~
a = 1..5
b = a.collect{|i| i += 2}
p b #=> [3, 4, 5, 6, 7]
~~~
在上面的例子中,接收者為范圍對象,而結果則是數組對象。像這樣,迭代器和數組就被緊密地結合在一起了。
### **13.8 處理數組中的元素**
對數組中的元素進行處理時可以采取多種方法。
### **13.8.1 使用循環與索引**
傳統的方法是使用循環,也就是在遍歷數組的同時,利用索引逐個訪問數組元素。
例如,在代碼清單 13.1 中,我們把數組的元素逐個取出來并輸出。
**代碼清單 13.1 list.rb**
~~~
list = ["a", "b", "c", "d"]
for i in 0..3
print "第", i+1,"個元素是",list[i],"。\n"
end
~~~
代碼清單 13.2 的程序是對數值數組的元素進行合計的例子。
**代碼清單 13.2 sum_list.rb**
~~~
list = [1, 3, 5, 7, 9]
sum = 0
for i in 0..4
sum += list[i]
end
print "合計:",sum,"\n"
~~~
### **13.8.2 使用 each 方法逐個獲取元素**
在數組中,通過 `each` 方法可以實現循環操作。下面,我們嘗試使用 `each` 方法來改寫代碼清單 13.2(代碼清單 13.3)。
**代碼清單 13.3 sum_list2.rb**
~~~
list = [1, 3, 5, 7, 9]
sum = 0
list.each do |elem|
sum += elem
end
print "合計:",sum,"\n"
~~~
但是,使用 `each` 方法時,我們并不知道元素的索引值。因此,當需要指定元素的索引值時,可以使用 `each_with_index` 方法。
**代碼清單 13.4 list2.rb**
~~~
list = ["a", "b", "c", "d"]
list.each_with_index do |elem, i|
print "第", i+1,"個元素是",elem,"。\n"
end
~~~
### **13.8.3 使用具有破壞性的方法實現循環**
如果數組內各元素全部處理完畢后該數組就不需要了,那么我們就可以通過逐個刪除數組元素使數組變空這樣的手段來實現循環。
~~~
while item = a.pop
## 對item 進行處理
end
~~~
假設在循環開始前已經有元素在數組 `a` 中。如果逐個刪除數組 `a` 中的元素,就會對刪除的元素進行處理。最后,當數組為空時,循環結束。
### **13.8.4 使用其他迭代器**
Ruby 中實現了不少像 `collect`、`map` 方法這樣一眼就能看出其作用的基本操作。當希望創建某種迭代器時,請翻閱 Ruby 參考手冊,一般情況下在里面都能找到我們需要的迭代器。這樣就不會因為花精力創建了一個 Ruby 本來就有的迭代器而感到失望了。
### **13.8.5 創建專用的迭代器**
不過有時也可能會找不到自己想要的迭代器,這時就只能根據需要創建屬于自己的迭代器了。
關于迭代器的創建,請參考 11.3 節。
### **13.9 數組的元素**
數組中可以存放各種各樣的對象。除了數值、字符串外,我們還可以在數組對象中存放別的數組對象或散列對象等等。
### **13.9.1 使用簡單的矩陣**
下面我們來看看用數組來表示矩陣的例子。
數組的各個元素也可以是數組,也就是所謂的數組的數組,這樣的形式經常被用于表示矩陣。
例如,我們試試用數組的數組這種形式來表示圖 13.15 那樣的矩陣。

**圖 13.15 3 行 3 列的矩陣**
第 1 行為 `[1, 2, 3]`,第 2 行為 `[4, 5, 6]`,第 3 行為 `[7, ,8 , 9]`,把它們歸納為數組,如下所示。
~~~
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
~~~
如果想取出元素 `6`,我們可以像下面這樣做。
~~~
a[1][2]
~~~
首先用 `a[1]` 表示 `[4, 5, 6]` 這個數組,然后再指定第 3 位的元素,這樣就能達到我們的目的了。
### **13.9.2 初始化時的注意事項**
把數組對象或者散列對象作為數組元素時,需要注意該對象初始化時的問題。
~~~
a = Array.new(3, [0, 0, 0])
~~~
在上面的例子中,我們可能會以為 `a` 為 `[[0, 0, 0], [0, 0, 0], [0, 0, 0]]`,但實際卻是另外一個結果(圖 13.16)。

**圖 13.16 數組的初始化**
像下面那樣,原本只是打算變更第 1 行的第 2 個元素,結果所有行的第 2 個元素都發生了改變。
~~~
a = Array.new(3, [0, 0, 0])
a[0][1] = 2
p a #=> [[0, 2, 0], [0, 2, 0], [0, 2, 0]]
~~~
為了解決這個問題,我們可以指定 `new` 方法的塊和元素個數。程序調用與元素個數一樣次數的塊,然后再將塊的返回值賦值給元素。每次調用塊都會生成新的對象,這樣一來,各個元素引用同一個對象的問題就不會發生了。
~~~
a = Array.new(3) do
[0, 0, 0]
end
p a #=> [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
a[0][1] = 2
p a #=> [[0, 2, 0], [0, 0, 0], [0, 0, 0]]
~~~
進行下述操作后,對應的元素的索引值就會被賦值給 `i`,這樣就可以根據索引值初始化出不同的值了。
~~~
a = Array.new(5){|i| i + 1 }
p a #=> [1, 2, 3, 4, 5]
~~~
### **13.10 同時訪問多個數組**
接下來我們來看看用相同的索引值同時訪問對多個數組時的情況。在代碼清單 13.5 中,合計三個數組中索引值相同的元素,并將結果保存在新數組(result)中。
**代碼清單 13.5 sum_with_each.rb**
~~~
ary1 = [1, 2, 3, 4, 5]
ary2 = [10, 20, 30, 40, 50]
ary3 = [100, 200, 300, 400, 500]
i = 0
result = []
while i < ary1.length
result << ary1[i] + ary2[i] + ary3[i]
i += 1
end
p result #=> [111, 222, 333, 444, 555]
~~~
使用 `zip` 方法可以程序變得更簡單(代碼清單 13.6)。
**代碼清單 13.6 sum_with_zip.rb**
~~~
ary1 = [1, 2, 3, 4, 5]
ary2 = [10, 20, 30, 40, 50]
ary3 = [100, 200, 300, 400, 500]
result = []
ary1.zip(ary2, ary3) do |a, b, c|
result << a + b + c
end
p result #=> [111, 222, 333, 444, 555]
~~~
`zip` 方法會將接收器和參數傳來的數組元素逐一取出,而且每次都會啟動塊。參數可以是一個也可以是多個。
> **專欄**
> **Enumerable 模塊**
> 介紹完 `Comparable` 模塊后,我們再來介紹一下常被用于 Mix-in 的 `Enumerable` 模塊。Enumerable 的意思是“可以被計數的”、“可以被列舉的”。在本書介紹過的類中,`Array`、`Dir`、`File`、`Hash`、`IO`、`Range`、`Enumerator` 等類中都包含了 `Enumerable` 模塊。
> **表 Enumerable 模塊定義的方法**
| `all?` | `any?` | `chunk` | `collect` |
|-----|-----|-----|-----|
| `collect_concat` | `count` | `cycle` | `detect` |
| `drop` | `drop_while` | `each_cons` | `each_entry` |
| `each_slice` | `each_with_index` | `each_with_object` | `entries` |
| `find` | `find_all` | `find_index` | `first` |
| `flat_map` | `grep` | `group_by` | `include?` |
| `inject` | `lazy` | `map` | `max` |
| `max_by` | `member?` | `min` | `min_by` |
| `minmax` | `minmax_by` | `none?` | `one?` |
| `partition` | `reduce` | `reject` | `reverse_each` |
| `select` | `slice_before` | `sort` | `sort_by` |
| `take` | `take_while` | `to_a` | `zip` |
> 本章中介紹的 `Array` 類的方法中,實際上有些是在 `Enumerable` 模塊中定義的。關于上文中沒有介紹的方法的用法,請參考 Ruby 參考手冊。
> 就像 `Comparable` 模塊需要 `<=>` 運算符那樣,`Enumerable` 模塊則需要 `each` 方法。例如,如果用 Ruby 來實現 `each_with_index` 方法的話,大概就是下面這樣(實際的程序會更加復雜,例如會有沒有塊時返回 `Enumerator` 對象等處理)。
~~~
module Enumerable
def each_with_index
index = 0 # 初始化索引
each do |item|
yield(item, index) # 將元素與index 作為參數
# 執行塊
index += 1 # 累加索引值
end
end
end
~~~
> 我們在創建提供循環處理的類的時候,可以首先創建迭代器 `each` 方法,然后再包含 `Enumerable` 模塊,這樣一來,上表的方法就都可以使用了。
# **練習題**
1. 創建一個數組 `a`,使 1 到 100 的整數按升序排列(即 `a[0]` 為 1,`a[99]` 為 100)。
2. 將 1 數組中的各元素擴大 100 倍,創建新數組 `a2`(即 `a2[0]` 為 100)。另外,不創建新數組,將原數組中的各元素都擴大 100 倍。
3. 獲取 1 數組中值是 3 的倍數的元素,創建新數組 `a3`(即 `a3[0]` 為 3,`a3[2]` 為 9)。另外,不創建新數組,把 3 的倍數以外的元素全部刪除。
4. 將 1 的數組按倒序排列。
5. 求 1 數組中的整數的和。
6. 從 1 到 100 的整數數組中,取出 10 個分別包含 10 個元素的數組,如 1 ~ 10,11 ~ 20,21 ~ 30。再將取出的全部數組按順序保存到數組 `result` 中,請考慮以下代碼中 `???` 的部分應該怎么寫。
~~~
ary = [ 包含1~100 的整數數組 ]
result = Array.new
10.times do |i|
result << ary[???]
end
~~~
7. 定義方法 `sum_array`,合計數組 `nums1` 和 `nums2` 中相對應的各個元素的值,并將合計結果作為數組返回。
~~~
p sum_array([1, 2, 3], [4, 6, 8]) #=> [5, 8, 11]
~~~
> 參考答案:請到圖靈社區本書的“隨書下載”處下載([http://www.ituring.com.cn/book/1237](http://www.ituring.com.cn/book/1237))。
- 推薦序
- 譯者序
- 前言
- 本書的讀者對象
- 第 1 部分 Ruby 初體驗
- 第 1 章 Ruby 初探
- 第 2 章 便利的對象
- 第 3 章 創建命令
- 第 2 部分 Ruby 的基礎
- 第 4 章 對象、變量和常量
- 第 5 章 條件判斷
- 第 6 章 循環
- 第 7 章 方法
- 第 8 章 類和模塊
- 第 9 章 運算符
- 第 10 章 錯誤處理與異常
- 第 11 章 塊
- 第 3 部分 Ruby 的類
- 第 12 章 數值類
- 第 13 章 數組類
- 第 14 章 字符串類
- 第 15 章 散列類
- 第 16 章 正則表達式類
- 第 17 章 IO 類
- 第 18 章 File 類與 Dir 類
- 第 19 章 Encoding 類
- 第 20 章 Time 類與 Date 類
- 第 21 章 Proc 類
- 第 4 部分 動手制作工具
- 第 22 章 文本處理
- 第 23 章 檢索郵政編碼
- 附錄
- 附錄 A Ruby 運行環境的構建
- 附錄 B Ruby 參考集
- 后記
- 謝辭