SlideShare una empresa de Scribd logo
1 de 13
Descargar para leer sin conexión
第五章

循环和迭代
大部分的程序设计中都会涉及到重复。也许你想你的程序连续发出十次”哔”的声音,或者
从一个文件中读入多行文本并将其打印出来,除非用户输入按键打印一个警告。Ruby 提供
了很多执行这种重复工作的途径。


For 循环
在很多的程序设计语言中,当你想将一段代码运行指定次数,你可以将其放到一个 for 循环
中。在大部分语言中,你给 for 循环一个变量,并将其初始化为一个初值,在每次循环中递
增 1,直到它的值等于指定的终止值。当该变量值等于终止值,for 循环停止。这里有一个
在 Pascal 语言中的一个传统的 for 循环:

  (* This is Pascal code, not Ruby! *)

  for i := 1 to 3 do

      writeln( i );

                                         for_loop.rb

你可以回顾一下上一章中我们所讲到的 Ruby 的 for 循环跟这个一点都不像。Ruby 中不再
提供一个初始值和终止值,而是给 for 循环提供一个项列表,然后一个一个地迭代该列表,
依此将每项值赋给循环变量,直到列表的结尾。

例如,这是一个 for 循环,在该循环中迭代一个数组中的所有项,然后将其值依序打印出来:

  # This is Ruby code...

  for i in [1,2,3] do

      puts( i )

  end

这个 for 循环更像一些其他语言中提供的 for each 迭代。循环迭代的项没必要都是整型数,
这样也是可以的...

  for s in ['one','two','three'] do

      puts( s )

  end
Ruby 的作者将集合类型 Array,Sets,Hashes 和 Strings(和 String 一样,但事实上是字符集
合)实现的 each 方法描述为 for 循环的”语法糖”。为了比较,这是一个上面循环的 each
方法表现形式:

                                                   each_loop.rb

  [1,2,3].each do |i|

      puts( i )

  end

如你所见,这两种方式几乎没有差别。将 for 循环转化为 each 迭代式,我所需要做的就是
删掉 for 和 in,然后在数组后追加一个.each。然后我将迭代变量 i 放到 do 后面一对竖线中。
比较以下的例子,看看 for 循环和 each 迭代之间有多么的相似:

                                                   for_each.rb

  # --- Example 1 ---

  # i) for

  for s in ['one','two','three'] do

      puts( s )

  end

  # ii) each

  ['one','two','three'].each do |s|

      puts( s )

  end

  # --- Example 2 ---

  # i) for

  for x in [1, "two", [3,4,5] ] do puts( x ) end

  # ii) each

  [1, "two", [3,4,5] ].each do |x| puts( x ) end

另外,在 for 循环分多行书写时,do 关键字是可选的(可以不写),但是如果当整个循环在一
行书写时,do 关键字是必须的:

  # Here the „do‟ keyword can be omitted
for s in ['one','two','three']

      puts( s )

  end

  # But here it is required

  for s in ['one','two','three'] do puts( s ) end
                                                      for_to.rb


  如何书写一个”标准的” for 循环 ...
  如果你依然想念传统的 for 循环,你可以通过使用 for 循环来迭代一个区间伪造一个。例
  如,现在需要使用一个 for 循环来将从 1 到 10 的数字依次打印出来:
        for i in (1..10) do
           puts( i )
        end
                                                    for_each2.rb

这个例子是关于如何使用 for 和 each 来迭代区间中的值的:

  # for

  for s in 1..3

      puts( s )

  end

  # each

  (1..3).each do |s|

      puts(s)

  end

另外,注意一个像 1..3 这样的区间表达式,在使用 each 方法时必须使用圆括号括起来,否
则 Ruby 将会假定你想使用常量的 each 方法而不是整个表达式的 each 方法。但是在 for 循
环中,这个括号是可选的。


复合迭代参数
                                                    multi_array.rb

你可以回顾一下上一章,我们在一个 for 循环中使用了多个循环变量。我们通过这种方式来
迭代一个多维数组。在每一个 for 循环中,一个变量被赋值为外部数组中的一行(也就是一个
子数组):

    # Here multiarr is an array containing two „rows‟

    # (sub-arrays) at index 0 and 1

    multiarr = [      ['one','two','three','four'],

                            [1,2,3,4]

                ]

    # This for loop runs twice (once for each „row‟ of multiarr)

    for (a,b,c,d) in multiarr

        print("a=#{a}, b=#{b}, c=#{c}, d=#{d}n" )

    end

上面的循环将打印如下结果:

    a=one, b=two, c=three, d=four

    a=1, b=2, c=3, d=4

我们可以通过传递四”块参数“(a,b,c,d)来使用 each 方法迭代这个四项数组,块参数由 do
和 end 来界定:

    multiarr.each do |a,b,c,d|

        print("a=#{a}, b=#{b}, c=#{c}, d=#{d}n" )

    end

     块参数
     在 Ruby 中,一个迭代体称为一个”块”,在迭代顶部包含在一对竖线中的变量都称为”块
     变量”。也就是说,一个块像一个函数般运行,块参数和函数参数列表相似。 each 方法运
     行在块中的代码,并将一个集合 ( 如数组 multiarr) 提供的值传递给块中代码。在上面的例
     子中, each 方法重复传递一个四元素的数组给循环中的代码块,这些元素用来初始化这四
     个块参数 a,b,c,d 。块还可以被用于其他的事情,如迭代集合等。我将在第十章详细介绍块。




块
                                                               block_syntax.rb

Ruby 还有一种语法可以用来界定块。不使用 do..end,而使用花括号{..},如下:
# do..end

  [[1,2,3],[3,4,5],[6,7,8]].each do

      |a,b,c|

      puts( "#{a}, #{b}, #{c}" )

  end

  # curly braces {..}

  [[1,2,3],[3,4,5],[6,7,8]].each{

      |a,b,c|

      puts( "#{a}, #{b}, #{c}" )

  }

不论你界定哪个块,你必须确认开放界定符'{'或者'do'必须是和 each 方法在同一行。在
each 方法和开放块界定符之间插入行是一个语法错误。


While 循环
Ruby 也有一些其他的循环结构。这是一个 while 循环:



  while tired

      sleep

  end

或者,可以这样写:

  sleep while tired

即使这两个例子的语法不一样,但是它们执行的效果是一样的。在第一个例子中,while 和
end 之间的代码(调用一个名为 sleep 的方法)将一直执行,只要 Boolean 条件(这里该值由
一个名为 tired 方法返回)一直为 true。和 for 循环一样,当判断语句和循环体需要执行代码
分别的单独行的时候 do 关键字可以省略;当条件判断和执行代码在同一行时,必须显式书
写 do 关键字。


While 变体
在第二个循环版本中(sleep while tired),执行代码(sleep)位于条件判断(while tired)之前。
这种语法成为”while modifier”。如果你想使用这个语法执行多个表达式,你可以将这些
表达式写到 begin 和 end 关键字之间。

  begin

     sleep

     snore

  end while tired
                                    1loops.rb


这个例子描述了这种语法的众多替代方式:

  $hours_asleep = 0

  def tired

     if $hours_asleep >= 8 then

     $hours_asleep = 0

     return false

     else

     $hours_asleep += 1

     return true

        end

  end



  def snore

     puts('snore....')

  end



  def sleep

  puts("z" * $hours_asleep )

  end
while tired do sleep end        # a single-line while loop



  while tired               # a multi-line while loop

     sleep

  end



  sleep while tired           # single-line while modifier



  begin

     sleep

     snore

  end while tired

上面的最后一个例子(那个多行的 while 变体)需要着重强调一下,因为它将引入一些很重要
的特性。当一个由 begin 和 end 界定的代码块在 while 条件判断之前,那么这段代码将至
少执行一次。在一些其他的 while 循环类型中,这些代码将永远不会执行除非布尔条件的值
为 true。

   确保一个循环至少执行一次
   通常一个 while 循环执行 0 次或者多次,因为布尔判断总是在循环执行之前进行的;如果布
   尔判断在外面返回 false ,那么在循环内部的代码将永远不会被执行。
   然而当我们将 while 测试放到一个由 begin 和 end 包含的代码段之后,这个循环中的代码
   就将至少执行一次



                                                               2loops.rb


    为了突出这两种 while 循环的区别,运行一下 2loops.rb 就可以看出来了。
    这些例子应该有助于更为清楚地描述该区别:
          x = 100
          # The code in this loop never runs
          while (x < 100) do puts('x < 100') end
          # The code in this loop never runs
          puts('x < 100') while (x < 100)
          # But the code in loop runs once
          begin puts('x < 100') end while (x < 100)
Until 循环
Ruby 还提供了一个 Until 循环,可以看作是一个”while not”循环。它的语法和基本特征
和 while 循环相似,条件判断语句和循环体内代码可以写在同一行(此时 do 关键字是必须的)
或者将其分别写到单独的行(此时 do 是可选的)。

同样也有一个 until 变体,可以让你将代码方在条件测试语句之前,并且可以使用 begin 和
end 来界定代码,以便确保该代码至少会运行一次。
                                                                 until.rb

这里有些 until 循环的例子:

  i = 10

  until i == 10 do puts(i) end         # never executes



  until i == 10             # never executes

     puts(i)

     i += 1

  end



  puts(i) until i == 10          # never executes



  begin                                        # executes once

     puts(i)

  end until i == 10

While 和 until 循环都像一个 for 循环,可以用来迭代数组和其他集合。例如,这是迭代数
组中所有元素的一个例子:

  while i < arr.length

     puts(arr[i])

     i += 1
end



  until i == arr.length

        puts(arr[i])

        i +=1

  end


循环
                                     3loops.rb

在 3loops.rb 中的例子看起来应该很熟悉——除最后一个之外:

  loop {

        puts(arr[i])

        i+=1

        if (i == arr.length) then

        break

        end

  }

这个例子使用 loop 方法来重复执行一个由花括号括起来的代码段。这就像我们之前使用
each 方法来执行块循环一般。又一次,我们可以选择块的界定符———花括号或者 do 和
end 关键字:

  puts( "nloop" )

  i=0

  loop do

        puts(arr[i])

        i+=1

        if (i == arr.length) then

        break

        end
end

这段代码通过一个计数变量 i 来迭代数组 arr,当(i==arr.length)为 true 时跳出循环。你必
须使用这种方式来跳出循环,不像 while 或者 until,loop 方法没有通过一个条件判断的值
来判断是否要继续循环。如果没有 break 关键字,它将一直循环下去。


深入探讨
哈希,数组,区间和集合都包含一个 Ruby 模块——Enumerable。一个模块就是一种代码
库(我将在第十二章详细介绍模块)。在第四章中,我使用了 Comparable 模块来给数组添加
一个比较方法,如<和>。你应该记起来了,我通过定义一个 Array 类的子类并包含
Comparable 模块来实现的:

  class Array2 < Array

      include Comparable

  end


Enumerable 模块
                                                           enum.rb

Enumerable 模块已经包含在 Ruby 的 Array 类中,它为 Array 类提供了很多有用的方法如
include?方法,该方法当某指定值包含于该数组中返回 true,min 方法用于返回数组中最小
值,max 返回数组中最大值,collect 创建一个新数组,该数组由一个代码段返回值组成:

  arr = [1,2,3,4,5]

  y = arr.collect{ |i| i }    #=> y = [1, 2, 3, 4, 5]

  z = arr.collect{ |i| i * i } #=> z = [1, 4, 9, 16, 25]

  arr.include?( 3 )           #=> true

  arr.include?( 6 )           #=> false

  arr.min                    #=> 1

  arr.max                    #=> 5
                                                           enum2.rb

这些方法在其他的包含了 Enumerable 模块的类中同样有效。Hash 就是其一。记住,然而
Hash 中的项不是顺序索引的,所以当你使用 min 和 max 方法时,这些方法返回的最小和
最大项是按照它们的字面值大小来计算的——当项为字符串时,字面值是由 key 中字符的
ASCII 码决定的。


自定义比较
但是假设你更倾向于让 min 和 max 方法的返回值基于其他的准则(比方说字符串的长度)。
我们最早的实现方式就是在一个代码块中定义一个比较。这种方式和我在第四章中实现的排
序块方式很相似。你也许想起来了我们通过传递一个代码块给 sort 方法来给一个 Hash 排序
(变量 h),如下:

  h.sort{ |a,b| a.to_s <=> b.to_s }

两个参数 a 和 b,分别代表 Hash 中两个使用<=>进行比较的项。我们可以类似地给 max
和 min 方法传递代码块:

  h.min{ |a,b| a[0].length <=> b[0].length }

  h.max{|a,b| a[0].length <=> b[0].length }

一个 Hash 传递项给一个代码块和传递给数组相同,每一个项都是一个键值对。所以,如果
一个 Hash 包含这样的项:

  {'one'=>'for sorrow', 'two'=>'for joy'}

那么这两个块参数 a 和 b 将被初始化为两个数组:

  a = ['one','for sorrow']

  b = ['two','for joy']

这就解释了为什么在我定义的 max 和 min 方法中的自定义比较中这两个块只比较两个块参
数索引值 0 处的元素:

  a[0].length <=> b[0].length

这确保比较是基于 Hash 中键值进行的。

如果你想比较值而不是键,只需要将数组的索引值设为 1:
                                                                enum3.rb

  p( h.min{|a,b| a[1].length <=> b[1].length } )

  p( h.max{|a,b| a[1].length <=> b[1].length } )

你当然也可以在你的代码块中定义其他的自定义比较类型。假设你想让'one','two','three'这
等,能按我们平时读的顺序来进行计算。一个方法就是创建一个有序的字符串数组:

  str_arr = ['one','two','three','four','five','six','seven']
如果一个哈希 h 包含这些字符串作为键值,那么在代码块中可以使用 str_arr 作为一个参考
来判定最大值和最小值:

  h.min{|a,b| str_arr.index(a[0]) <=> str_arr.index(b[0])}

  #=> ["one", "for sorrow"]

  h.max{|a,b| str_arr.index(a[0]) <=> str_arr.index(b[0])}

     #=> ["seven", "for a secret never to be told"]

上面所有的示例,都使用了 Array 和 Hash 的 min 和 max 方法。记住,这些方法是由
Enumerable 模块为这些类提供的。

当我们在使用一些其他类并不是继承于那些已经实现了 max,min 和 collect 方法的类(如
Array)时,能将 Enumerable 中的 max,min 和 collect 方法应用于这些类之中将是非常有
用的。你可以通过在你的类中包含 Enumerable 模块,然后编写一个名为 each 的迭代器方
法,如下:
                                                               inclue_enum1.rb

  class MyCollection

     include Enumerable

     def initialize( someItems )

     @items = someItems

     end

     def each

     @items.each{ |i|

         yield( i )

     }

     end

  end

你可以使用一个数组来初始化一个 MyCollection 对象,数组将保存在实例变量@items 中。
你可以调用由 Enumerable 模块秘密地提供的方法(如 min,max 或者 collect),调用 each 方
法每次获取 data 中的一项。

现在你可以通过你的 MyCollection 对象使用 Enumerable 的方法:

  things = MyCollection.new(['x','yz','defgh','ij','klmno'])
p( things.min )                       #=> "defgh"

  p( things.max )                       #=> "yz"

  p( things.collect{ |i| i.upcase } )

                     #=> ["X", "YZ", "DEFGH", "IJ", "KLMNO"]
                                                               inclue_enum2.rb

你可以类似地使用你的 MyCollection 类来处理数组,继而处理 Hash 的键和值。当前 min
和 max 方法采用默认的行为来进行比较,那就是基于字面值大小,所以基于 ASCII 码
值'xy'将被视为比'abcd'要大。如果你想做一些其他类型的比较,比方说,通过字符串长度,
那么'abcd'就会被认为大于'xz'——你完全可以重写 min 和 max 方法:
                                                               inclue_enum3.rb

  def min

      @items.to_a.min{|a,b| a.length <=> b.length }

  end



  def max

      @items.to_a.max{|a,b| a.length <=> b.length }

  end

    Each 和 Yield...
    那么事情上当 Enumerable 模块中的一个方法使用你自己编写的 each 方法时将发生什么
    呢?结果是 Enumerable 方法 (min,max,collect 等等 ) 传 递 一个 代 码 块 给 each 方法。这
                                                     代
    个代码块将一次接受一个数据 ( 即某种集合中的每一项 ) 。你的 each 方法通过 一 个 块 参 数
                                               一
    的形式提供该 数 据 , 就 像 这 里 的 参 数 i:
             据,就像 里的
        def each{
            @items.each{ |i|
        yield( i )
        }
        end
    关键字 yield 是 Ruby 中的一点小魔法,它告诉代码运行传递给 each 方法的代码块——也
    就是说,运行由 Enumerable 模块提供的 min,max 和 collect 方法。这意味着这些方法的
    代码可以被不同的集合使用。你所要做的就是, 1) 包含 Enumerable 模块到你的类中 ;2)
    编写一个 each 方法,该方法决定 Enumerable 方法将使用哪个值。

Más contenido relacionado

La actualidad más candente

sed -- A programmer's perspective
sed -- A programmer's perspectivesed -- A programmer's perspective
sed -- A programmer's perspectiveLi Ding
 
系統程式 -- 第 7 章 高階語言
系統程式 -- 第 7 章 高階語言系統程式 -- 第 7 章 高階語言
系統程式 -- 第 7 章 高階語言鍾誠 陳鍾誠
 
Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版Harvey Zhang
 
系統程式 -- 第 11 章 嵌入式系統
系統程式 -- 第 11 章 嵌入式系統系統程式 -- 第 11 章 嵌入式系統
系統程式 -- 第 11 章 嵌入式系統鍾誠 陳鍾誠
 
系統程式 -- 第 12 章 系統軟體實作
系統程式 -- 第 12 章 系統軟體實作系統程式 -- 第 12 章 系統軟體實作
系統程式 -- 第 12 章 系統軟體實作鍾誠 陳鍾誠
 
系統程式 -- 第 3 章 組合語言
系統程式 -- 第 3 章 組合語言系統程式 -- 第 3 章 組合語言
系統程式 -- 第 3 章 組合語言鍾誠 陳鍾誠
 
Bash shell script 教學
Bash shell script 教學Bash shell script 教學
Bash shell script 教學Ming-Sian Lin
 
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7Justin Lin
 
mysql的字符串函数
mysql的字符串函数mysql的字符串函数
mysql的字符串函数wensheng wei
 
系統程式 -- 第 5 章 連結與載入
系統程式 -- 第 5 章 連結與載入系統程式 -- 第 5 章 連結與載入
系統程式 -- 第 5 章 連結與載入鍾誠 陳鍾誠
 
竞赛中C++语言拾遗
竞赛中C++语言拾遗竞赛中C++语言拾遗
竞赛中C++语言拾遗乐群 陈
 
Python学习笔记
Python学习笔记Python学习笔记
Python学习笔记Lingfei Kong
 
PHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHP
PHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHPPHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHP
PHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHPLi-Wei Lu
 
音乐歌词同步
音乐歌词同步音乐歌词同步
音乐歌词同步Bin Shao
 

La actualidad más candente (19)

sed -- A programmer's perspective
sed -- A programmer's perspectivesed -- A programmer's perspective
sed -- A programmer's perspective
 
系統程式 -- 第 7 章 高階語言
系統程式 -- 第 7 章 高階語言系統程式 -- 第 7 章 高階語言
系統程式 -- 第 7 章 高階語言
 
Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版
 
系統程式 -- 第 11 章 嵌入式系統
系統程式 -- 第 11 章 嵌入式系統系統程式 -- 第 11 章 嵌入式系統
系統程式 -- 第 11 章 嵌入式系統
 
系統程式 -- 第 12 章 系統軟體實作
系統程式 -- 第 12 章 系統軟體實作系統程式 -- 第 12 章 系統軟體實作
系統程式 -- 第 12 章 系統軟體實作
 
SCJP ch10
SCJP ch10SCJP ch10
SCJP ch10
 
系統程式 -- 第 3 章 組合語言
系統程式 -- 第 3 章 組合語言系統程式 -- 第 3 章 組合語言
系統程式 -- 第 3 章 組合語言
 
系統程式 - 第二章
系統程式 - 第二章系統程式 - 第二章
系統程式 - 第二章
 
Bash shell script 教學
Bash shell script 教學Bash shell script 教學
Bash shell script 教學
 
Hi Haskell
Hi HaskellHi Haskell
Hi Haskell
 
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
 
mysql的字符串函数
mysql的字符串函数mysql的字符串函数
mysql的字符串函数
 
系統程式 -- 附錄
系統程式 -- 附錄系統程式 -- 附錄
系統程式 -- 附錄
 
系統程式 -- 第 5 章 連結與載入
系統程式 -- 第 5 章 連結與載入系統程式 -- 第 5 章 連結與載入
系統程式 -- 第 5 章 連結與載入
 
竞赛中C++语言拾遗
竞赛中C++语言拾遗竞赛中C++语言拾遗
竞赛中C++语言拾遗
 
Python学习笔记
Python学习笔记Python学习笔记
Python学习笔记
 
PHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHP
PHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHPPHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHP
PHP 初階課程 Part. 3 - Functions and brief intro to Object-Oriented PHP
 
音乐歌词同步
音乐歌词同步音乐歌词同步
音乐歌词同步
 
第四章 串
第四章 串第四章 串
第四章 串
 

Destacado

Arc Ims Html Viewer Refrence
Arc Ims Html Viewer RefrenceArc Ims Html Viewer Refrence
Arc Ims Html Viewer Refrence贺 利华
 
介绍&第一章
介绍&第一章介绍&第一章
介绍&第一章贺 利华
 
第四章 休閒事業的策略性行銷管理
第四章   休閒事業的策略性行銷管理第四章   休閒事業的策略性行銷管理
第四章 休閒事業的策略性行銷管理蜨穆 諾淦
 

Destacado (6)

Arc Ims Html Viewer Refrence
Arc Ims Html Viewer RefrenceArc Ims Html Viewer Refrence
Arc Ims Html Viewer Refrence
 
第二章
第二章第二章
第二章
 
第六章
第六章第六章
第六章
 
第四章
第四章第四章
第四章
 
介绍&第一章
介绍&第一章介绍&第一章
介绍&第一章
 
第四章 休閒事業的策略性行銷管理
第四章   休閒事業的策略性行銷管理第四章   休閒事業的策略性行銷管理
第四章 休閒事業的策略性行銷管理
 

Similar a 第五章

Learning notes ruby
Learning notes rubyLearning notes ruby
Learning notes rubyRoger Xia
 
来自 Google 的 r 语言编码风格指南
来自 Google 的 r 语言编码风格指南来自 Google 的 r 语言编码风格指南
来自 Google 的 r 语言编码风格指南学峰 司
 
Cypher 查询语言
Cypher 查询语言Cypher 查询语言
Cypher 查询语言zernel
 
Learning python in the motion picture industry by will zhou
Learning python in the motion picture industry   by will zhouLearning python in the motion picture industry   by will zhou
Learning python in the motion picture industry by will zhouWill Zhou
 
Ihome inaction 篇外篇之fp介绍
Ihome inaction 篇外篇之fp介绍Ihome inaction 篇外篇之fp介绍
Ihome inaction 篇外篇之fp介绍dennis zhuang
 
6, awk
6, awk6, awk
6, awkted-xu
 
Groovy简介
Groovy简介Groovy简介
Groovy简介profeter
 
第9章 Shell 編程
第9章 Shell 編程第9章 Shell 編程
第9章 Shell 編程kidmany2001
 
第3章算法与控制语句
第3章算法与控制语句第3章算法与控制语句
第3章算法与控制语句summerfeng
 
Python learn guide
Python learn guidePython learn guide
Python learn guiderobin yang
 
线程与并发
线程与并发线程与并发
线程与并发Tony Deng
 
Bash入门基础篇
Bash入门基础篇Bash入门基础篇
Bash入门基础篇Zhiyao Pan
 
JCConf 2023 - 深入淺出 Java 21 功能
JCConf 2023 - 深入淺出 Java 21 功能JCConf 2023 - 深入淺出 Java 21 功能
JCConf 2023 - 深入淺出 Java 21 功能Joseph Kuo
 
物件導向設計原理及設計樣式
物件導向設計原理及設計樣式物件導向設計原理及設計樣式
物件導向設計原理及設計樣式Y YU
 
Maintainable PHP Source Code
Maintainable PHP Source CodeMaintainable PHP Source Code
Maintainable PHP Source CodeBo-Yi Wu
 
Linux常用命令与工具简介
Linux常用命令与工具简介Linux常用命令与工具简介
Linux常用命令与工具简介weihe
 

Similar a 第五章 (20)

Learning notes ruby
Learning notes rubyLearning notes ruby
Learning notes ruby
 
来自 Google 的 r 语言编码风格指南
来自 Google 的 r 语言编码风格指南来自 Google 的 r 语言编码风格指南
来自 Google 的 r 语言编码风格指南
 
Ruby basic
Ruby basicRuby basic
Ruby basic
 
Cypher 查询语言
Cypher 查询语言Cypher 查询语言
Cypher 查询语言
 
Learning python in the motion picture industry by will zhou
Learning python in the motion picture industry   by will zhouLearning python in the motion picture industry   by will zhou
Learning python in the motion picture industry by will zhou
 
Ihome inaction 篇外篇之fp介绍
Ihome inaction 篇外篇之fp介绍Ihome inaction 篇外篇之fp介绍
Ihome inaction 篇外篇之fp介绍
 
6, awk
6, awk6, awk
6, awk
 
Groovy简介
Groovy简介Groovy简介
Groovy简介
 
第9章 Shell 編程
第9章 Shell 編程第9章 Shell 編程
第9章 Shell 編程
 
Ch10 範例
Ch10 範例Ch10 範例
Ch10 範例
 
第3章算法与控制语句
第3章算法与控制语句第3章算法与控制语句
第3章算法与控制语句
 
Python learn guide
Python learn guidePython learn guide
Python learn guide
 
线程与并发
线程与并发线程与并发
线程与并发
 
Bash入门基础篇
Bash入门基础篇Bash入门基础篇
Bash入门基础篇
 
JCConf 2023 - 深入淺出 Java 21 功能
JCConf 2023 - 深入淺出 Java 21 功能JCConf 2023 - 深入淺出 Java 21 功能
JCConf 2023 - 深入淺出 Java 21 功能
 
nodeMCU IOT教學02 - Lua語言
nodeMCU IOT教學02 - Lua語言nodeMCU IOT教學02 - Lua語言
nodeMCU IOT教學02 - Lua語言
 
nodeMCU IOT教學02 - Lua語言
nodeMCU IOT教學02 - Lua語言nodeMCU IOT教學02 - Lua語言
nodeMCU IOT教學02 - Lua語言
 
物件導向設計原理及設計樣式
物件導向設計原理及設計樣式物件導向設計原理及設計樣式
物件導向設計原理及設計樣式
 
Maintainable PHP Source Code
Maintainable PHP Source CodeMaintainable PHP Source Code
Maintainable PHP Source Code
 
Linux常用命令与工具简介
Linux常用命令与工具简介Linux常用命令与工具简介
Linux常用命令与工具简介
 

第五章

  • 1. 第五章 循环和迭代 大部分的程序设计中都会涉及到重复。也许你想你的程序连续发出十次”哔”的声音,或者 从一个文件中读入多行文本并将其打印出来,除非用户输入按键打印一个警告。Ruby 提供 了很多执行这种重复工作的途径。 For 循环 在很多的程序设计语言中,当你想将一段代码运行指定次数,你可以将其放到一个 for 循环 中。在大部分语言中,你给 for 循环一个变量,并将其初始化为一个初值,在每次循环中递 增 1,直到它的值等于指定的终止值。当该变量值等于终止值,for 循环停止。这里有一个 在 Pascal 语言中的一个传统的 for 循环: (* This is Pascal code, not Ruby! *) for i := 1 to 3 do writeln( i ); for_loop.rb 你可以回顾一下上一章中我们所讲到的 Ruby 的 for 循环跟这个一点都不像。Ruby 中不再 提供一个初始值和终止值,而是给 for 循环提供一个项列表,然后一个一个地迭代该列表, 依此将每项值赋给循环变量,直到列表的结尾。 例如,这是一个 for 循环,在该循环中迭代一个数组中的所有项,然后将其值依序打印出来: # This is Ruby code... for i in [1,2,3] do puts( i ) end 这个 for 循环更像一些其他语言中提供的 for each 迭代。循环迭代的项没必要都是整型数, 这样也是可以的... for s in ['one','two','three'] do puts( s ) end
  • 2. Ruby 的作者将集合类型 Array,Sets,Hashes 和 Strings(和 String 一样,但事实上是字符集 合)实现的 each 方法描述为 for 循环的”语法糖”。为了比较,这是一个上面循环的 each 方法表现形式: each_loop.rb [1,2,3].each do |i| puts( i ) end 如你所见,这两种方式几乎没有差别。将 for 循环转化为 each 迭代式,我所需要做的就是 删掉 for 和 in,然后在数组后追加一个.each。然后我将迭代变量 i 放到 do 后面一对竖线中。 比较以下的例子,看看 for 循环和 each 迭代之间有多么的相似: for_each.rb # --- Example 1 --- # i) for for s in ['one','two','three'] do puts( s ) end # ii) each ['one','two','three'].each do |s| puts( s ) end # --- Example 2 --- # i) for for x in [1, "two", [3,4,5] ] do puts( x ) end # ii) each [1, "two", [3,4,5] ].each do |x| puts( x ) end 另外,在 for 循环分多行书写时,do 关键字是可选的(可以不写),但是如果当整个循环在一 行书写时,do 关键字是必须的: # Here the „do‟ keyword can be omitted
  • 3. for s in ['one','two','three'] puts( s ) end # But here it is required for s in ['one','two','three'] do puts( s ) end for_to.rb 如何书写一个”标准的” for 循环 ... 如果你依然想念传统的 for 循环,你可以通过使用 for 循环来迭代一个区间伪造一个。例 如,现在需要使用一个 for 循环来将从 1 到 10 的数字依次打印出来: for i in (1..10) do puts( i ) end for_each2.rb 这个例子是关于如何使用 for 和 each 来迭代区间中的值的: # for for s in 1..3 puts( s ) end # each (1..3).each do |s| puts(s) end 另外,注意一个像 1..3 这样的区间表达式,在使用 each 方法时必须使用圆括号括起来,否 则 Ruby 将会假定你想使用常量的 each 方法而不是整个表达式的 each 方法。但是在 for 循 环中,这个括号是可选的。 复合迭代参数 multi_array.rb 你可以回顾一下上一章,我们在一个 for 循环中使用了多个循环变量。我们通过这种方式来
  • 4. 迭代一个多维数组。在每一个 for 循环中,一个变量被赋值为外部数组中的一行(也就是一个 子数组): # Here multiarr is an array containing two „rows‟ # (sub-arrays) at index 0 and 1 multiarr = [ ['one','two','three','four'], [1,2,3,4] ] # This for loop runs twice (once for each „row‟ of multiarr) for (a,b,c,d) in multiarr print("a=#{a}, b=#{b}, c=#{c}, d=#{d}n" ) end 上面的循环将打印如下结果: a=one, b=two, c=three, d=four a=1, b=2, c=3, d=4 我们可以通过传递四”块参数“(a,b,c,d)来使用 each 方法迭代这个四项数组,块参数由 do 和 end 来界定: multiarr.each do |a,b,c,d| print("a=#{a}, b=#{b}, c=#{c}, d=#{d}n" ) end 块参数 在 Ruby 中,一个迭代体称为一个”块”,在迭代顶部包含在一对竖线中的变量都称为”块 变量”。也就是说,一个块像一个函数般运行,块参数和函数参数列表相似。 each 方法运 行在块中的代码,并将一个集合 ( 如数组 multiarr) 提供的值传递给块中代码。在上面的例 子中, each 方法重复传递一个四元素的数组给循环中的代码块,这些元素用来初始化这四 个块参数 a,b,c,d 。块还可以被用于其他的事情,如迭代集合等。我将在第十章详细介绍块。 块 block_syntax.rb Ruby 还有一种语法可以用来界定块。不使用 do..end,而使用花括号{..},如下:
  • 5. # do..end [[1,2,3],[3,4,5],[6,7,8]].each do |a,b,c| puts( "#{a}, #{b}, #{c}" ) end # curly braces {..} [[1,2,3],[3,4,5],[6,7,8]].each{ |a,b,c| puts( "#{a}, #{b}, #{c}" ) } 不论你界定哪个块,你必须确认开放界定符'{'或者'do'必须是和 each 方法在同一行。在 each 方法和开放块界定符之间插入行是一个语法错误。 While 循环 Ruby 也有一些其他的循环结构。这是一个 while 循环: while tired sleep end 或者,可以这样写: sleep while tired 即使这两个例子的语法不一样,但是它们执行的效果是一样的。在第一个例子中,while 和 end 之间的代码(调用一个名为 sleep 的方法)将一直执行,只要 Boolean 条件(这里该值由 一个名为 tired 方法返回)一直为 true。和 for 循环一样,当判断语句和循环体需要执行代码 分别的单独行的时候 do 关键字可以省略;当条件判断和执行代码在同一行时,必须显式书 写 do 关键字。 While 变体 在第二个循环版本中(sleep while tired),执行代码(sleep)位于条件判断(while tired)之前。
  • 6. 这种语法成为”while modifier”。如果你想使用这个语法执行多个表达式,你可以将这些 表达式写到 begin 和 end 关键字之间。 begin sleep snore end while tired 1loops.rb 这个例子描述了这种语法的众多替代方式: $hours_asleep = 0 def tired if $hours_asleep >= 8 then $hours_asleep = 0 return false else $hours_asleep += 1 return true end end def snore puts('snore....') end def sleep puts("z" * $hours_asleep ) end
  • 7. while tired do sleep end # a single-line while loop while tired # a multi-line while loop sleep end sleep while tired # single-line while modifier begin sleep snore end while tired 上面的最后一个例子(那个多行的 while 变体)需要着重强调一下,因为它将引入一些很重要 的特性。当一个由 begin 和 end 界定的代码块在 while 条件判断之前,那么这段代码将至 少执行一次。在一些其他的 while 循环类型中,这些代码将永远不会执行除非布尔条件的值 为 true。 确保一个循环至少执行一次 通常一个 while 循环执行 0 次或者多次,因为布尔判断总是在循环执行之前进行的;如果布 尔判断在外面返回 false ,那么在循环内部的代码将永远不会被执行。 然而当我们将 while 测试放到一个由 begin 和 end 包含的代码段之后,这个循环中的代码 就将至少执行一次 2loops.rb 为了突出这两种 while 循环的区别,运行一下 2loops.rb 就可以看出来了。 这些例子应该有助于更为清楚地描述该区别: x = 100 # The code in this loop never runs while (x < 100) do puts('x < 100') end # The code in this loop never runs puts('x < 100') while (x < 100) # But the code in loop runs once begin puts('x < 100') end while (x < 100)
  • 8. Until 循环 Ruby 还提供了一个 Until 循环,可以看作是一个”while not”循环。它的语法和基本特征 和 while 循环相似,条件判断语句和循环体内代码可以写在同一行(此时 do 关键字是必须的) 或者将其分别写到单独的行(此时 do 是可选的)。 同样也有一个 until 变体,可以让你将代码方在条件测试语句之前,并且可以使用 begin 和 end 来界定代码,以便确保该代码至少会运行一次。 until.rb 这里有些 until 循环的例子: i = 10 until i == 10 do puts(i) end # never executes until i == 10 # never executes puts(i) i += 1 end puts(i) until i == 10 # never executes begin # executes once puts(i) end until i == 10 While 和 until 循环都像一个 for 循环,可以用来迭代数组和其他集合。例如,这是迭代数 组中所有元素的一个例子: while i < arr.length puts(arr[i]) i += 1
  • 9. end until i == arr.length puts(arr[i]) i +=1 end 循环 3loops.rb 在 3loops.rb 中的例子看起来应该很熟悉——除最后一个之外: loop { puts(arr[i]) i+=1 if (i == arr.length) then break end } 这个例子使用 loop 方法来重复执行一个由花括号括起来的代码段。这就像我们之前使用 each 方法来执行块循环一般。又一次,我们可以选择块的界定符———花括号或者 do 和 end 关键字: puts( "nloop" ) i=0 loop do puts(arr[i]) i+=1 if (i == arr.length) then break end
  • 10. end 这段代码通过一个计数变量 i 来迭代数组 arr,当(i==arr.length)为 true 时跳出循环。你必 须使用这种方式来跳出循环,不像 while 或者 until,loop 方法没有通过一个条件判断的值 来判断是否要继续循环。如果没有 break 关键字,它将一直循环下去。 深入探讨 哈希,数组,区间和集合都包含一个 Ruby 模块——Enumerable。一个模块就是一种代码 库(我将在第十二章详细介绍模块)。在第四章中,我使用了 Comparable 模块来给数组添加 一个比较方法,如<和>。你应该记起来了,我通过定义一个 Array 类的子类并包含 Comparable 模块来实现的: class Array2 < Array include Comparable end Enumerable 模块 enum.rb Enumerable 模块已经包含在 Ruby 的 Array 类中,它为 Array 类提供了很多有用的方法如 include?方法,该方法当某指定值包含于该数组中返回 true,min 方法用于返回数组中最小 值,max 返回数组中最大值,collect 创建一个新数组,该数组由一个代码段返回值组成: arr = [1,2,3,4,5] y = arr.collect{ |i| i } #=> y = [1, 2, 3, 4, 5] z = arr.collect{ |i| i * i } #=> z = [1, 4, 9, 16, 25] arr.include?( 3 ) #=> true arr.include?( 6 ) #=> false arr.min #=> 1 arr.max #=> 5 enum2.rb 这些方法在其他的包含了 Enumerable 模块的类中同样有效。Hash 就是其一。记住,然而 Hash 中的项不是顺序索引的,所以当你使用 min 和 max 方法时,这些方法返回的最小和 最大项是按照它们的字面值大小来计算的——当项为字符串时,字面值是由 key 中字符的
  • 11. ASCII 码决定的。 自定义比较 但是假设你更倾向于让 min 和 max 方法的返回值基于其他的准则(比方说字符串的长度)。 我们最早的实现方式就是在一个代码块中定义一个比较。这种方式和我在第四章中实现的排 序块方式很相似。你也许想起来了我们通过传递一个代码块给 sort 方法来给一个 Hash 排序 (变量 h),如下: h.sort{ |a,b| a.to_s <=> b.to_s } 两个参数 a 和 b,分别代表 Hash 中两个使用<=>进行比较的项。我们可以类似地给 max 和 min 方法传递代码块: h.min{ |a,b| a[0].length <=> b[0].length } h.max{|a,b| a[0].length <=> b[0].length } 一个 Hash 传递项给一个代码块和传递给数组相同,每一个项都是一个键值对。所以,如果 一个 Hash 包含这样的项: {'one'=>'for sorrow', 'two'=>'for joy'} 那么这两个块参数 a 和 b 将被初始化为两个数组: a = ['one','for sorrow'] b = ['two','for joy'] 这就解释了为什么在我定义的 max 和 min 方法中的自定义比较中这两个块只比较两个块参 数索引值 0 处的元素: a[0].length <=> b[0].length 这确保比较是基于 Hash 中键值进行的。 如果你想比较值而不是键,只需要将数组的索引值设为 1: enum3.rb p( h.min{|a,b| a[1].length <=> b[1].length } ) p( h.max{|a,b| a[1].length <=> b[1].length } ) 你当然也可以在你的代码块中定义其他的自定义比较类型。假设你想让'one','two','three'这 等,能按我们平时读的顺序来进行计算。一个方法就是创建一个有序的字符串数组: str_arr = ['one','two','three','four','five','six','seven']
  • 12. 如果一个哈希 h 包含这些字符串作为键值,那么在代码块中可以使用 str_arr 作为一个参考 来判定最大值和最小值: h.min{|a,b| str_arr.index(a[0]) <=> str_arr.index(b[0])} #=> ["one", "for sorrow"] h.max{|a,b| str_arr.index(a[0]) <=> str_arr.index(b[0])} #=> ["seven", "for a secret never to be told"] 上面所有的示例,都使用了 Array 和 Hash 的 min 和 max 方法。记住,这些方法是由 Enumerable 模块为这些类提供的。 当我们在使用一些其他类并不是继承于那些已经实现了 max,min 和 collect 方法的类(如 Array)时,能将 Enumerable 中的 max,min 和 collect 方法应用于这些类之中将是非常有 用的。你可以通过在你的类中包含 Enumerable 模块,然后编写一个名为 each 的迭代器方 法,如下: inclue_enum1.rb class MyCollection include Enumerable def initialize( someItems ) @items = someItems end def each @items.each{ |i| yield( i ) } end end 你可以使用一个数组来初始化一个 MyCollection 对象,数组将保存在实例变量@items 中。 你可以调用由 Enumerable 模块秘密地提供的方法(如 min,max 或者 collect),调用 each 方 法每次获取 data 中的一项。 现在你可以通过你的 MyCollection 对象使用 Enumerable 的方法: things = MyCollection.new(['x','yz','defgh','ij','klmno'])
  • 13. p( things.min ) #=> "defgh" p( things.max ) #=> "yz" p( things.collect{ |i| i.upcase } ) #=> ["X", "YZ", "DEFGH", "IJ", "KLMNO"] inclue_enum2.rb 你可以类似地使用你的 MyCollection 类来处理数组,继而处理 Hash 的键和值。当前 min 和 max 方法采用默认的行为来进行比较,那就是基于字面值大小,所以基于 ASCII 码 值'xy'将被视为比'abcd'要大。如果你想做一些其他类型的比较,比方说,通过字符串长度, 那么'abcd'就会被认为大于'xz'——你完全可以重写 min 和 max 方法: inclue_enum3.rb def min @items.to_a.min{|a,b| a.length <=> b.length } end def max @items.to_a.max{|a,b| a.length <=> b.length } end Each 和 Yield... 那么事情上当 Enumerable 模块中的一个方法使用你自己编写的 each 方法时将发生什么 呢?结果是 Enumerable 方法 (min,max,collect 等等 ) 传 递 一个 代 码 块 给 each 方法。这 代 个代码块将一次接受一个数据 ( 即某种集合中的每一项 ) 。你的 each 方法通过 一 个 块 参 数 一 的形式提供该 数 据 , 就 像 这 里 的 参 数 i: 据,就像 里的 def each{ @items.each{ |i| yield( i ) } end 关键字 yield 是 Ruby 中的一点小魔法,它告诉代码运行传递给 each 方法的代码块——也 就是说,运行由 Enumerable 模块提供的 min,max 和 collect 方法。这意味着这些方法的 代码可以被不同的集合使用。你所要做的就是, 1) 包含 Enumerable 模块到你的类中 ;2) 编写一个 each 方法,该方法决定 Enumerable 方法将使用哪个值。