Fork me on GitHub

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!

blog comments powered by Disqus
About

RSS

All Posts IT Ruby functional programming

Tags

GitHub projects

Twitter @botanicus

Recent Comments