ruby系列教材(24):Implementing Iterators

上一篇 / 下一篇  2008-02-02 15:26:49 / 个人分类:Ruby

Block的内部调用过程:

 

调用method three_times的时候,遇见yield,就立即调用Block,block运行完毕,马上返回yield的下一句

 

method_name(parameter,parameter){block}

这里,解释器遇见method_name(parameter,parameter)就进入method_name(parameter,parameter)的定义体,运行,当遇见block的时候,就执行{}中的内容,也就是解释器遇见method_name(parameter,parameter)时候并不关心后面的内容,也就是block的内容,只是在运行遇见yield statement的时候才转到block这里,所以在class里面定义以后,调用时候必须时候,否则将出现错误:

def test

  yield

end

test

 

结果:

in `test': no block given (LocalJumpError)
    from -:4

 

不过这里说了:ruby解释器,先了解method_name(parameter,parameter)就进入class definition,遇见yield就运行block,所以下面的代码也是合法的:

def test

   p "run"    #没有yield,也可以运行

end

test{}

 

结果:

run

 

 

Block可以接收一个来自yield处的变量,也可以返回一个值

1)Block接收值

值(value)来源于yield,例如
def fib_up_to(max)

  i1,i2 = 1,1                                      # parallel assignment (i1 = 1 and i2 = 1)

  while i1 <= max

    yield i1                                         #i1讲被传递到block里面

    i1,i2 = i2,i1+i2

  end

end

 

fib_up_to(1000){|f| print f," "}        #i1的值赋值给f,表现就是i1的值传递到了block里面的f了,使用|variable|来接受值

 

关于block的应用我们很久以前就说过了,假如yield有2个parameters,那么这里要用|para1,para2|这样的形式

 

前面我们提过,不能从语法的角度来理解ruby,要从语意的角度理解ruby,因为ruby是一门更加贴近问题域的语言

这里yield i1有一层语意就是,我要把 i1 这个值传递给一个block

 

我们下面看一段程序:

a = [1,2]
b = 'cat'
a.each{|b| c = b * a[1]}


问题出现了,1)a,b在括号里面出现,会不会改变其值 2)c在括号外面可以用吗?

 

结果是:

a→[1, 2]

b→2                        #值被括号内改变

defined?(c)→nil        #c没有被定义,也就是c出了括号,就没有了

 

事实上有2条规则:

a. block外面的variables出现在block内部,内部将直接改变值

b. block外面的variables没有出现在block内部,这时候的variables作用域仅仅在这个block内或者说是属于这个block

 

这样我们得到了block和外部环境交互的能力,但是这样的方式也遭到了很多的质疑,也许会改今后的版本中进行一定的调整

 

*注意,一般的情况,比如find,each,times这些迭代器都是从0开始到max结束

find适用于Array,yield带有1个parameter,迭代过程从0-array.length,返回值是element类型(or nil)
each适用于Array,yield带有1个parameter,迭代过程从0-array.length,返回值是这个array本身
times适用于Fixnum,yield带有1个parameter,迭代过程从0-(num-1),返回值是这个fixnum

上面的总结不全面,注意,对于each,times我们根本不关注他们的返回类型

 

 

2)Block返回值

一个block可以返回一个值,可以认为一个yield(parameter,parameter)可以返回一个值,这个值是block中,最后一次赋值的表达式的值(同于methods)

 

我们前面提到过find iterator,可能大家会觉得有些迷惑,它的实现如下:

class Array

  def find

    for i in 0...self.length             #self表示引用它的object

      value = self[i]

      return value if yield(value)  #yield返回一个值,这里返回的是一个true or false

    end

    return nil

  end

end

 

*上面的self.length可以写成 size,表示引用它的对象的大小

 

self 表示应用这个method的object,例如,在method里面有self,123.method_name 这个时候,self表示123这个object

 

yield 有什么好处呢?yield实现了代码级的复用,我们一般来说,实现的是method级别的复用,也就是复用方法,而yield提供了这样的能力,使得我们重复出现的代码都消失了,这是十分神奇的

 

 

Iterator:(一般来说只要是collection就有他的iterators)

1)each

遍历array中的所有element,对于array来说,可以这样用:

[1,2,3,4,5].each{|i| p i}

 

对于File class each iterator每次从file object里面每次读出一行:

f =  File.open("testfile")

f.each do |line|              #一次读出一行

  puts line

end

f.close

 

2)collect

和each一样进行遍历,但是collect将所有的block的返回值收集起来,建立一个array object返回,例如:

num = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
num2 = num.collect do |i|
    if i%2==0
        i
    else
        []
    end
end

num2.each{|i| print i," "}

 

结果:

 2  4  6  8  10  12  14 

 

3)inject

inject可以带parameter,inject的parameter和yield的parameter有一定的关系,yield有2个parameters

object.inject(a){|p1,p2| p1+p2}

这个表示p1初始化为a,p1以后的值为block的返回值,p2是object elements的值,一直遍历过去

举例说明:

print [1,2,4,9].inject(0){|sum,ele| sum+ele}    #结果:16

print [1,2,4,9].inject(1){|sum,ele| sum*ele}     #结果:72

 

inject也可以不带parameter,这个时候,yield第一个parameter的值为array object第一个element的值,yield第2个parameter的值是array object的第2个element的值,比如:

print [1,2,4,9].inject{|sum,ele| sum*ele}     #结果:72    .............1)

print [1,2,4,9].inject{|sum,ele| sum+ele}    #结果:16     .............2)

1)中sum初始化的值是1,ele最初值是2

2)中sum初始化的值是1,ele最初值是2


TAG: Ruby

 

评分:0

我来说两句

我的栏目

日历

« 2024-05-03  
   1234
567891011
12131415161718
19202122232425
262728293031 

数据统计

  • 访问量: 22822
  • 日志数: 47
  • 建立时间: 2008-01-29
  • 更新时间: 2008-02-02

RSS订阅

Open Toolbar