Ruby中Enumerable#inject用法示范

Enumerable#inject是Ruby核心库中的一个简洁而且强大的API,今天读到一段简洁的代码之后,对这个API产生了浓厚的兴趣,索性搜寻一下资料,总结一下它的用法。

代码如下:

 

def text_at(*args)
  args.inject(@feed) { |s, r| s.send(:at, r)}.inner_text
end

这段代码完成的功能是:取出XML文件中某子元素的文本内容,它是用nokogiri库来完成这个功能的。关于Nokogiri库API(at(), inner_text())的细节我们不谈,只是用这段代码来引起你对inject的兴趣,现在我们来看inject的用法。

 

Enumerable#inject是很多Ruby高手喜欢的API,因此将来读Ruby也会经常遇到,即使有的用法不能完全理解,也要先混个脸熟,使用场景见多了就自然理解了。

 

1. 数字求和

Inject最常见的用法就是这一个了,假定你有一个内容为数字的数组,你可以像下面这样对它求和:

irb(main):001:0> [1, 2, 3, 4, 5].inject(0) { |sum, e| sum + e }
=> 15

或者像这样:

irb(main):002:0> (1..5).inject(0) { |sum, e| sum + e }
=> 15

用区间或者数组在这里没有分别,Ruby会自动转换。Inject在这里接受一个方法参数和一个block. 对数组中的每一个元素,block都执行一次。第一次执行block的时候,inject接收的方法参数被作为block的第一个参数,而block的第二个参数则是数组的第一个元素。第二次执行block的时候,情况就有了变化,这也是inject神奇的地方。这时候block的第一个参数则是block上一次执行的返回值(block的最后一个表达式),第二个参数则是数组的第二个元素,后面的三次执行方式与第二次相同,这样我们就计算出了数组元素之和。事实上,上面的代码还可以更简洁一些:

irb(main):006:0> [1, 2, 3, 4, 5].inject { |sum, e| sum + e }
=> 15

这段代码可以计算出相同结果的原因是:inject的方法参数是可选的,如果不提供的话,Ruby默认将数组的第一个元素作为block第一次执行时候的第一个参数,在这种情况下,block一共需要执行4次,比传入默认参数的形式少执行一次。

 

2. 转换数据结构

2.1生成Hash:

hash = [[:first_name, 'Shane'], [:last_name, 'Harvie']].inject({}) do |result, element|
  result[element.first] = element.last
  result
end

 当然这种用法也有别的形式,并不一定需要用到inject,比如:

Hash[*[[:first_name, 'Shane'], [:last_name, 'Harvie']].flatten]

 可以达到相同目的而代码更简洁。

 

2.2 过滤数组:

arr = [1, 2, 3, 4, 5, 6].inject([]) do |r, e|
  r << e.to_s if e % 2 == 0
  r
end

 

当然这种用法也有不使用inject的等价方式:

[1, 2, 3, 4, 5, 6].select { |e| e % 2 == 0 }.collect { |e| e.to_s } # => ["2", "4", "6"]

 具体用哪一种就是萝卜青菜,各有所爱了。

 

3. 更高级的用法

先看一段代码:

class Recorder
  # undefine any instance methods except methods start from __
  # and inspect/to_str/object_id
  instance_methods.each do |meth|
    undef_method meth unless meth =~ /^(__|inspect|to_str)|object_id/
  end

  # store messages sent to the Recorder object
  # that's why it's called Recorder
  def method_missing(sym, *args)
    messages << [sym, args]
    self
  end

  # getter of instance variable messages
  def messages
    @messages ||= []
  end
end

代码中比较难懂的部分已经加了注释,类Recorder完成的功能是记录所有发送给它对象的消息(Ruby中对象调用通常称作向对象发送消息)。下面我们再打开类Recorder,定义另一方法来展示这些保存的消息:

class Recorder
  def play_for(obj)
    messages.inject(obj) do |result, msg|
      result.send msg.first, *msg.last
    end
  end
end

在这个方法中,我们用到了inject, 它向传入的对象obj调用保存的消息,并传入之前调用者传入的所有参数。下面的代码展现了它的用法:

recorder = Recorder.new
recorder.methods.sort
recorder.play_for(String)

它实现了对String对象(你应该可以想起来,Ruby的类也是对象)调用#methods(), 然后对#methods返回结果调用#sort().

其实上面这个Recorder示例和本文开头的那个范例原理相同,前一个调用可以响应第一个消息,返回的结果则分别可以响应接下来的消息,对比这两个示例可以对Enumerable#inject的强大之处有所体会。

 

参考:http://blog.jayfields.com/2008/03/ruby-inject.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值