I'm So In Love with Enumerable#inject
This post has been updated at 16/05/2013.
Enumerable#inject is the most flexible method for iterating collections with awesome possibilities. Let's take a look on some examples where it actually fits better than other Enumerable methods.
Enumerable#inject is the most flexible method for iterating collections with awesome possibilities. Let's take a look on some examples where it actually fits better than other Enumerable methods.
Basic Explanation
It seems there is still a lot of folks who don't know how to work with inject, so let me quickly explain. Enumerable#inject takes self, go through all the items, pass each item as the second argument for the block. The first argument is value returned for previous iteraction, or, in case this is the first one, an argument specified for inject. If the argument isn't provided, then the first and the second value of self is used as an arguments for the block. Confused? Take a look at some examples:
(1..10).inject { |sum, item| sum + item }
# => 55
# simple simulation of Enumerable#all?
[true, false, true].inject { |boolean, item| boolean && item }
# => false
# since Hash#map returns an array, we might rather use Hash#inject
{a: 1, b: 2, c: 3}.inject(Hash.new) { |result, pair| result.merge(pair[0] => pair[1].to_s) }
=> {a: "1", b: "2", c: "3"}
So as you can see, you can return whatever value type you want: string, collection or boolean, it's up to you.
Handy, right? Another great thing is that you can add items to an already existing collection:
data = attrs.delete(:data) || Hash.new
data.inject(attrs) { |hash, pair| hash.merge!("data-#{pair[0]}" => pair[1]) }
This is from one of my HTML helpers, I want to specify some custom data to the HTML (which are prefixed by data-, according to HTML 5 specification), so I just want to do something like: {class: "autocompleter", data: {type: "product"}} and get class="autocomplater" data-type="product" etc.
The last thing I want to show up is how that you can avoid setting global state for tasks like parsing thanks to the fact that you can access the value returned from the last iteraction. Take a look for example to my changelog:
def parse
@result ||= begin
lines = File.readlines(@path)
lines.inject([nil, Hash.new]) do |pair, line|
version, hash = pair
if line.match(/^=/)
version = line.chomp.sub(/^= /, "")
hash[version] = Array.new
elsif line.match(/^\s+\* /)
hash[version].push(line.chomp.sub(/^\s+\* /, ""))
else # skip empty lines
end
[version, hash]
end.last
end
end
Do you have some handy inject tricks? I'll be glad if you'll show them in comments!
