迭代器

Iterators

迭代器

Iterators are not an original concept with ruby.

迭代器并不是Ruby的原创概念。

They are in common use in object-oriented languages.

它们在面向对象的语言中是通用的。

They are also used in Lisp, though there they are not called iterators. However the concept of iterator is an unfamiliar one for many so it should be explained in more detail.

它们也被用于Lisp中,尽管不被叫做迭代器。然而,迭代器的概念对于很多人来说都是陌生的,所以应该更详细地解释它。

The verb iterate means to do the same thing many times, you know, so an iterator is something that does the same thing many times.

动词迭代意味着多次执行相同的操作,所以迭代器就是多次执行相同操作的东西。

When we write code, we need loops in various situations. In C, we code them using for or while. For example,

当我们编写代码时,我们需要在不同的情况下循环。在C语言中,我们用for或者while来编写它们,举个例子:

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

C’s for(…) syntax provides an abstraction to help with the creation of a loop, but the test of *str against a null character requires the programmer to know details about the internal structure of a string.

Cfor(…)语法提供了一个抽象来帮助创建循环,但是,要测试*str是否为一个空的字符串,则要求程序员了解一个字符串内部结构的细节究竟是怎样的。

This makes C feel like a low-level language. Higher level languages are marked by their more flexible support for iteration. Consider the following sh shell script:

这让C感觉像是一种低级语言,更高级别的语言则通过对迭代的显式支持使其更加灵活。考虑下面的shell脚本:

  1. #!/bin/sh
  2. for i in *.[ch]; do
  3. # ... 这里是对每个文件要执行的操作
  4. done

All the C source and header files in the current directory are processed, and the command shell handles the details of picking up and substituting file names one by one. I think this is working at a higher level than C, don’t you?

当前目录中的所有C源文件和头文件都被处理了,并且命令shell将处理获取和替换文件名的详细信息。相比C,我认为这是在更高的层次上工作,你觉得呢?

But there is more to consider: while it is fine for a language to provide iterators for built-in data types, it is a disappointment if we must go back to writing low level loops to iterate over our own data types. In OOP, users often define one data type after another, so this could be a serious problem.

但是这里仍有一些问题要考虑:对于一种语言来说,为内置数据类型提供迭代器是很好的,如果我们必须回到编写低级别循环来迭代我们自己的数据类型,这将是一个令人失望的事情。在OOP中,用户经常定义一个数据类型,因此这可能是一个严重的问题。

So every OOP language includes some facilities for iteration. Some languages provide a special class for this purpose; ruby allows us to define iterators directly.

所以每个OOP语言都包含了一些用于迭代的工具,有一些语言提供了用于迭代的类,Ruby则允许我们直接定义迭代器

Ruby’s String type has some useful iterators:

Ruby字符串类型就有一些有用的迭代器:

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

each_byte is an iterator for each character in the string. Each character is substituted into the local variable c. This can be translated into something that looks a lot like C code …

each_byte是字符串中的每个字符的迭代器。每个字符都被局部变量c替换,这可以被翻译成看起来很像C代码的东西。

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

… however, the each_byte iterator is both conceptually simpler and more likely to continue to work even if the String class happens to be radically modified in the future.

然而,迭代器each_byte在概念上都更简单,即使字符串类在将来会被彻底修改,它仍可能会继续工作。

One benefit of iterators is that they tend to be robust in the face of such changes; indeed that is a characteristic of good code in general. (Yes, have patience, we’re about to talk about what classes are, too.)

迭代器的一个优点是,面对这样的变化时,它们往往是健壮的,这确实是好代码所拥有的特点。(是的,要有耐心,我们也要谈谈课程的内容。)

Another iterator of String is each_line.

字符串的另一个迭代器each_line

  1. ruby> "a\nb\nc\n".each_line{|l| print l}
  2. a
  3. b
  4. c
  5. nil

The tasks that would take most of the programming effort in C (finding line delimiters, generating substrings, etc.) are easily tackled using iterators.

使用迭代器可以很容易地完成C语言(查找行分隔符、生成子字符串等)的大多数编程工作。

The for statement appearing in the previous chapter does iteration by way of an each iterator. String’s each works the same as each_line, so let’s rewrite the above example with for:

在前一章节中出现的for语句通过迭代器的方法对每一个进行迭代,

  1. ruby> for l in "a\nb\nc\n"
  2. | print l
  3. | end
  4. a
  5. b
  6. c
  7. nil

We can use a control structure retry in conjunction with an iterated loop, and it will retry the loop from the beginning.

我们可以将迭代器循环和控制结构retry结合使用,它会从头开始重新尝试循环。

  1. ruby> c=0
  2. 0
  3. ruby> for i in 0..4
  4. | print i
  5. | if i == 2 and c == 0
  6. | c = 1
  7. | print "\n"
  8. | retry
  9. | end
  10. | end; print "\n"
  11. 012
  12. 01234
  13. nil

Replacing retry in the above example with redo causes just the current iteration of the loop to be redone, with this output:

在上面的例子中,用redo来替换retry,只会导致循环的当前迭代被重新执行,会有如下输出:

012
234
yield occurs sometimes in a definition of an iterator.

yield有时出现于迭代器的定义中。

yield moves control to the block of code that is passed to the iterator (this will be explored in more detail in the chapter about procedure objects).

yield将控制权移交到传递给迭代器的代码块(这将在关于过程对象的章节中更详细地探讨)。

The following example defines an iterator repeat, which repeats a block of code the number of times specified in an argument.

下面的示例定义了一个重复执行的迭代器,它重复一个代码块,重复的次数由传递给它的参数指定。

  1. ruby> def repeat(num)
  2. | while num > 0
  3. | yield
  4. | num -= 1
  5. | end
  6. | end
  7. nil
  8. ruby> repeat(3) { puts "foo" }
  9. foo
  10. foo
  11. foo
  12. nil

With retry, one can define an iterator which works something like ruby’s standard while.

通过retry,可以定义一个迭代器,它的工作方式类似于Ruby标准的while

  1. ruby> def WHILE(cond)
  2. | return if not cond
  3. | yield
  4. | retry
  5. | end
  6. nil
  7. ruby> i=0; WHILE(i<3) { print i; i+=1 }
  8. 012 nil

Do you understand what an iterator is? There are a few restrictions, but you can write your original iterators; and in fact, whenever you define a new data type, it is often convenient to define suitable iterators to go with it.

你明白迭代器是什么了么?这里有几个条件,但是你可以编写你自己的迭代器原型。事实上,无论在什么时候定义一个新的数据类型,定义合适的迭代器通常都是很方便的。

In this sense, the above examples are not terribly useful. We can talk about practical iterators after we have a better understanding of what classes are.

从这个意义上来说,上面的例子并不是很有用。在我们对课程有了更好的理解之后,我们可以讨论一些实际的迭代器。

上一章 流程控制
下一章 面向对象的思考