`
美洲狮
  • 浏览: 2539 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

ruby inject 用法(转载)

阅读更多

Ruby: inject

 
I love inject. To be more specific, I love Enumerable#inject. I find it easy to read and easy to use. It's powerful and it lets me be more concise. Enumerable#inject is a good thing.

Of course, I didn't always love it. When I was new to Ruby I didn't understand what it was, so I found it hard to follow. However, finding it hard to understand didn't make me run from it, instead I wanted to know what all the hype was about. Enumerable#inject is an often used method by many great Rubyists, and I wanted to know what I was missing.

So, to learn about Enumerable#inject I did what I always do, I used it every possible way I could think of.

Example 1: Summing numbers
Summing numbers is the most common example for using inject. You have an array of numbers and you want the sum of those numbers.

[1, 2, 3, 4].inject(0) {|result, element| result + element } # => 10

If the example isn't straightforward, don't worry, we're going to break it down. The inject method takes an argument and a block. The block will be executed once for each element contained in the object that inject was called on ([1,2,3,4] in our example). The argument passed to inject will be yielded as the first argument to the block, the first time it's executed. The second argument yielded to the block will be the first element of the object that we called inject on.

So, the block will be executed 4 times, once for every element of our array ([1,2,3,4]). The first time the block executes the result argument will have a value of 0 (the value we passed as an argument to inject) and the element argument will have a value of 1 (the first element in our array).

You can do anything you want within the block, but the return value of the block is very important. The return value of the block will be yielded as the result argument the next time the block is executed.

In our example we add the result, 0, to the element, 1. Therefore, the return value of the block will be 0 + 1, or 1. This will result in 1 being yielded as the result argument the second time the block is executed.

The second time the block is executed the result of the previous block execution, 1, will be yielded as the result, and the second element of the array will be yielded as the element. Again the result, 1, and the element, 2 will be added together, resulting in the return value of the block being 3.

The third time the block is executed the result of the second block execution, 3, is yielded as the result argument and the third element of the array, 3, will be yielded as the element argument. Again, the result and the element will be added, and the return value of the block for the third execution will be 6.

The fourth time will be the final time the block is executed since there are only 4 elements in our array. The result value will be 6, the result from the third execution of the block, and the element will be 4, the fourth element of the array. The block will execute, adding four plus six, and the return value of the block will be 10. On the final execution of the block the return value is used as the return value of the inject method; therefore, as the example shows, the result of executing the code above is 10.

That's the very long version of how inject works, but you could actually shortcut one of the block executions by not passing an argument to inject.

[1, 2, 3, 4].inject {|result, element| result + element } # => 10

As the example shows, the argument to inject is actually optional. If a default value is not passed in as an argument the first time the block executes the first argument (result from our example) will be set to the first element of the enumerable (1 from our example) and the second argument (element from our example) will be set to the second element of the enumerable (2 from our example). 

In this case the block will only need to be executed 3 times, since the first execution will yield both the first and the second element. The first time the block executes it will add the result, 1, to the element, 2, and return a value of 3. The second time the block executes the result will be 3 and the element will also be 3. All additional steps will be the same, and the result will be 10 once again.

Summing numbers with inject is a simple example of taking an array of numbers and building a resulting sum one element at a time.

Example 2: Building a Hash
Sometimes you'll have data in one format, but you really want it in another. For example, you may have an array that contains keys and values as pairs, but it's really just an array of arrays. In that case, inject is a nice solution for quickly converting your array of arrays into a hash.

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

hash # => {:first_name=>"Shane", :last_name=>"Harvie"}


As the example shows, I start with an empty hash (the argument to inject) and I iterate through each element in the array adding the key and value one at a time to the result. Also, since the result of the block is the next yielded result, I need to add to the hash, but explicitly return the result on the following line.

Ola Bini and rubikitch both pointed out that you can also create a hash from an array with the following code.

Hash[*[[:first_name, 'Shane'], [:last_name, 'Harvie']].flatten] # => {:first_name=>"Shane", :last_name=>"Harvie"}

Of course, I can do other things in inject also, such as converting the keys to be strings and changing the names to be lowercase. 

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

hash # => {"first_name"=>"shane", "last_name"=>"harvie"}

This is a central value for inject, it allows me to easily convert an enumerable into an object that is useful for the problem I'm trying to solve.

Example 3: Building an Array
Enumerable gives you many methods you need for manipulating arrays. For example, if want all the integers of an array, that are even, as strings, you can do so chaining various methods from Enumerable.

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

Chaining methods of Enumerable is a solution that's very comfortable for many developers, but as the chain gets longer I prefer to use inject. The inject method allows me to handle everything I need without having to chain multiple independent methods.

The code below achieves the same thing in one method, and is just as readable, to me.
array = [1, 2, 3, 4, 5, 6].inject([]) do |result, element|
  result << element.to_s if element % 2 == 0
  result
end

array # => ["2", "4", "6"]

Of course, that example is a bit contrived; however, a realistic example is when you have an object with two different properties and you want to build an array of one, conditionally based on the other. A more concrete example is an array of test result objects that know if they've failed or succeeded and they have a failure message if they've failed. For reporting, you want all the failure messages.

You can get this with the built in methods of Enumerable.

TestResult = Struct.new(:status, :message)
results = [
  TestResult.new(:failed, "1 expected but was 2"),
  TestResult.new(:sucess),
  TestResult.new(:failed, "10 expected but was 20")
]

messages = results.select {|test_result| test_result.status == :failed }.collect {|test_result| test_result.message }
messages # => ["1 expected but was 2", "10 expected but was 20"]

But, it's not obvious what you are doing until you read the entire line. You could build the array the same way using inject and if you are comfortable with inject it reads slightly cleaner.

TestResult = Struct.new(:status, :message)
results = [
  TestResult.new(:failed, "1 expected but was 2"),
  TestResult.new(:sucess),
  TestResult.new(:failed, "10 expected but was 20")
]

messages = results.inject([]) do |messages, test_result|
  messages << test_result.message if test_result.status == :failed
  messages
end
messages # => ["1 expected but was 2", "10 expected but was 20"]

I prefer to build what I want using inject instead of chaining methods of Enumerable and effectively building multiple objects on the way to what I need.

Example 4: Building a Hash (again)
Building from the Test Result example you might want to group all results by their status. The inject method lets you easily do this by starting with an empty hash and defaulting each key value to an empty array, which is then appended to with each element that has the same status.

TestResult = Struct.new(:status, :message)
results = [
  TestResult.new(:failed, "1 expected but was 2"),
  TestResult.new(:sucess),
  TestResult.new(:failed, "10 expected but was 20")
]

grouped_results = results.inject({}) do |grouped, test_result|
  grouped[test_result.status] = [] if grouped[test_result.status].nil?
  grouped[test_result.status] << test_result
  grouped
end

grouped_results
# >> {:failed => [
# >>    #<struct TestResult status=:failed, message="1 expected but was 2">, 
# >>    #<struct TestResult status=:failed, message="10 expected but was 20">],
# >>  :sucess => [ #<struct TestResult status=:sucess, message=nil> ]
# >> }

You might be sensing a theme here.

Example 5: Building a unknown result
Usually you know what kind of result you are looking for when you use inject, but it's also possible to use inject to build an unknown object.

Consider the following Recorder class that saves any messages you send it.

class Recorder
  instance_methods.each do |meth|
    undef_method meth unless meth =~ /^(__|inspect|to_str)/
  end

  def method_missing(sym, *args)
    messages << [sym, args]
    self
  end

  def messages
    @messages ||= []
  end
end

Recorder.new.will.record.anything.you.want
# >> #<Recorder:0x28ed8 @messages=[[:will, []], [:record, []], [:anything, []], [:you, []], [:want, []]]>

By simply defining the play_for method and using inject you can replay each message on the argument and get back anything depending on how the argument responds to the recorded methods.

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

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

# >> ["<", "<=", "<=>", "==", "===", "=~", ">", ">=", "__id__", ...]

THE AUTHOR PAGE(source)
http://blog.jayfields.com/2008/03/ruby-inject.html
分享到:
评论

相关推荐

    vue 解决provide和inject响应的问题

    官网上说provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。 provide: Object | () =&gt; Object(一个对象或返回一个对象的函数) inject...

    VC代码 RT_INJECT (实用代码源)

    VC代码 RT_INJECT (实用代码源)VC代码 RT_INJECT (实用代码源)VC代码 RT_INJECT (实用代码源)VC代码 RT_INJECT (实用代码源)VC代码 RT_INJECT (实用代码源)VC代码 RT_INJECT (实用代码源)VC代码 RT_INJECT (实用代码...

    javax-inject-1.0-201002241208.jar.zip

    在本文中,我们将深入探讨javax.inject的核心概念、使用方法以及与Hibernate框架的集成。 一、javax.inject简介 javax.inject包提供了三个主要的注解:@Inject、@Named和@Provider。这些注解帮助开发者声明和管理...

    官方jar包javax-inject.jar

    8. **使用方法**:在Java项目中,将`javax-inject.jar`添加到类路径后,开发者可以开始使用注解来声明和注入依赖。例如,通过`@Inject`注解一个字段,框架会在运行时自动填充合适的依赖对象。 综上所述,`javax-...

    InjectDLL_injectDll_

    Usage: injectdll.exe [process name] [dll path] [option number]option 1 - CreateRemoteThreadoption 2 - NtCreateThreadExoption 3 - RtlCreateUserThread

    javax.inject.Provider

    Spring 依赖的 javax.inject.Provider

    Ruby Data-Processing ruby数据处理

    在Ruby中,这三个方法常常一起使用,形成强大的数据处理流水线。比如,你可以先使用Map对数据进行转换,然后用Reduce进行聚合,最后通过Select进行过滤。这样的组合提供了灵活且高效的处理大量数据的方式。 阅读...

    javax-inject.jar.zip

    `javax.inject.Inject`注解是最核心的注解,它用于标注一个构造函数、字段或方法,表明这个构造函数、字段或方法的依赖应该由外部容器(如Google Guice、Spring框架等)自动注入,而不是由类自己创建。通过这种方式...

    ruby实用函数和实例

    首先,我们关注的是"使用Ruby语言实现自动测试与数据采集.pdf"这个文件。自动测试是软件开发过程中的重要环节,它确保代码的质量和可靠性。Ruby提供了几个知名的测试框架,如RSpec和Minitest。RSpec允许开发者以自然...

    CodeInject.rar

    code inject 逆向工程 做一些工具必备

    autoinject小工具

    通过使用AutoInject,我们可以更好地理解DLL注入的工作机制,同时也提醒我们在实际网络环境中要警惕这类攻击手段。学习并掌握这些知识,无论是对于提升个人技能,还是保障网络安全,都有着积极的意义。

    Ruby-ParallelRuby让并行处理简单和快速

    除了`each`方法,Parallel库还提供了其他方法,如`map`、`inject`等,这些都是并行版本的内置Ruby方法,方便在并行环境下使用熟悉的编程模式。 在实际应用中,理解并行处理的潜在问题也至关重要,如数据竞争、死锁...

    Inject HTTP.zip_Inject HTTP vb6_Inject2Download_http inject_inje

    2. **inject2download**:可能是指一种能够利用HTTP注入下载远程文件的方法或工具。 3. **http_inject**:强调了HTTP协议层面的注入活动。 4. **inject**:通用术语,表示注入行为,可能包括多种类型的注入攻击。 ...

    sisu-inject-bean-1.4.2.jar

    sisu-inject-bean-1.4.2.jar

    Location IOS13驱动版本,包含inject.dmg和inject.dmg.signature两个文件

    总之,这个压缩包包含的“inject.dmg”和“inject.dmg.signature”文件是iOS 13系统定位服务驱动的更新,提供增强的定位功能和安全性。在使用时,用户需要验证签名以确保文件的合法性,并根据设备的当前iOS版本进行...

    ruby的惯用法的使用

    - **注入操作**: Ruby中的`inject`方法类似于Python中的`reduce`函数。 ```ruby numbers = [1, 2, 3] sum = numbers.inject(0) { |total, num| total + num } # Python: from functools import reduce numbers...

    .javax.inject_1.0.0.jar

    1. `@Inject`:这是最基础的注解,用于标记一个字段、构造函数或者方法参数,表示该成员需要被依赖注入。容器会在运行时自动提供合适的实例。 2. `@Named`:这个注解用于为注入的对象指定一个唯一的名称。在有多个...

    apc_inject.zip_APC_APC inject_inject_inject apc_inject code

    标题 "apc_inject.zip_APC_APC inject_inject_inject apc_inject code" 提及的核心技术是“APC(Asynchronous Procedure Call)注入”,这通常与恶意软件或安全研究相关,特别是针对Windows操作系统的攻击手段。...

    Location IOS12.4驱动版本,包含inject.dmg和inject.dmg.signature两个文件

    2. 能耗优化:新驱动可能通过更高效的算法降低了定位服务对电池的影响,延长了设备的使用时间。 3. 隐私保护加强:可能增强了对用户位置数据的保护,比如限制第三方应用访问位置信息的权限,或者提供了更多的隐私...

Global site tag (gtag.js) - Google Analytics