Ruby 使用手冊

迭代器 Iterators

迭代器 (iterator) 並不是 Ruby 的原創概念, 這已在物件導向程式語言中普遍使用。它也被用在 Lisp 中,只是並不稱為迭代器而已。然而很多人仍然不熟悉迭代器的概念,因此以下將詳細解釋。

迭代 (iterate) 指的是重複做相同的事,所以迭代器 (iterator) 就是用來重複多次相同的事。

我們編寫程式碼後,需要在不同的情況下迴圈。使用 C 時,我會用 forwhile。例如,

char *str;
for (str = "abcdefg"; *str != '\0'; str++) {
  /* process a character here */
}

C 的 for(...) 語法提供建立迴圈的抽象概念,但測試空字元 (null character) 的 *str 程式碼,需要程式員了解字串的內部結構。如此讓人覺得 C 是低階的語言。高階語言應更能彈性地支援迭代。請看一下的 sh shell 腳本:

#!/bin/sh

for i in *.[ch]; do
  # ... 針對每個檔案執行某件事
done

處理目前目錄下的所有 C source 及標頭檔,由命令 shell 負責提取檔案名稱,並一一替代。這樣比 C 看來高階得多吧,你說呢?

但我們還要留意:雖然語言能夠為內建資料型態提供迭代器,但如果我們得為自定的資料型態編寫低階的迴圈才能迭代,就太令人失望了。畢竟使用 OOP(物件導向程式)時,我們經常要一一定義資料型態,這就成了嚴重的問題。

因此每種 OOP 語言都有些方式有助於迭代。有些語言會提供特殊的類別;Ruby 則讓我們直接定義迭代。

Ruby 字串 (string) 型態具有一些實用的迭代器:

ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n"
<a><b><c>
   nil

each_byte 是字串中用於處理每個字元的迭代器。每個字元都會代入區域變數 c 中。這段程式也可以改寫為類似 C 語言的寫法...

ruby> s="abc";i=0
   0
ruby> while i<s.length
    |    printf "<%c>", s[i]; i+=1
    | end; print "\n"
<a><b><c>
   nil

⋯⋯然而,each_byte 迭代器的概念較簡單,而且即使之後字串 (string) 類別的結構大幅改變,仍有較大機會繼續運作。迭代器的好處在於面對變化仍能保持耐用,確實稱得上是良好的程式碼。(好的,耐心點,我們很快就會討論類別 (class)

字串 (string) 的另一個迭代器是 each_line

ruby> "a\nb\nc\n".each_line{|l| print l}
a
b
c
   nil

比起 C 程式,迭代器更能事半功倍(尋找行分隔符號 (line delimiter)、建立子字串等)。

前一節的 for 敘述就是使用 each 迭代器進行迭代的。字串 (string)eacheach_line 效果相同(編註:Ruby 1.9 之後版本,移除了字串的 each 函式,所以必須改用 each_line。此例在 1.9 之後版本無法執行。),那麼就將上述的範例用 for 改寫:

ruby> for l in "a\nb\nc\n"
    |   print l
    | end
a
b
c
   nil

將控制結構 retry 與迭代的迴圈一起運作,就能重新開始迴圈。

ruby> c=0
   0
ruby> for i in 0..4
    |   print i
    |   if i == 2 and c == 0
    |     c = 1
    |     print "\n"
    |     retry
    |   end
    | end; print "\n"
012
01234
   nil

retry 替換為 redo,會令目前的迴圈重做一次迭代,輸出內容為:

012
234

yield 有時會出現在迭代器的定義中。yield 可將控制移至傳遞往迭代器的程式碼區塊 (block of code)(這將在程序物件一節中討論)。以下範例定義了迭代器 repeat,這個迭代器可按引數所指定的次數,重複程式碼區塊。

ruby> def repeat(num)
    |   while num > 0
    |     yield
    |     num -= 1
    |   end
    | end
   nil
ruby> repeat(3) { puts "foo" }
foo
foo
foo
   nil

使用 retry,就能定義類似 Ruby 標準的 while 迭代器。

ruby> def WHILE(cond)
    |   return if not cond
    |   yield
    |   retry
    | end
   nil
ruby> i=0; WHILE(i<3) { print i; i+=1 }
012   nil

你明白甚麼是迭代器了嗎?雖然仍有些限制,但你已經可以自行編寫迭代器。事實上,每當定義新資料型態時,常會一併定義合適的迭代器,方便作為搭配。因此,上述範例並不說得上非常有用。了解甚麼是類別 (class) 後,再來討論一些實用的迭代器吧。

Copyright (c) 2005-2008 Mark Slagell,中文翻譯 Ruby Taiwan 社群

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.

A copy of the license is included in the section entitled "GNU Free Documentation License."