03 August 2007 ~ 9 Comments

Ruby Inject

You probably frequently have to iterate through a list and accumulate a result that changes as you iterate. Let’s take the following array:

nums = [1, 3, 5, 7]

If you want to find a sum of all the items in the array, you could write something like this:

sum = 0
nums.each { |n| sum += n }

This works fine, but it doesn’t look much elegant. Luckly, for us that wants to be elegant there is a inject method.
(See: Enumerable::inject)

sum = nums.inject(0) { |x,n| x+n }

This code does the same thing, but also looks cool. Inject acomplish this by using accumulator or accumulated value (x). At each step accumulated value is set to the value returned by the block. Here, we set accumulator initial value to 0, and at each step current value from array (n) is added to accumulated value.

If initial value is omitted, the first item is used as the accumulator initial value and is then omitted from iteration.

sum = nums.inject { |x,n| x+n }

Is same as

sum = nums[0]
nums[1..-1].each { |n| sum += n }

Another example.
We have array with five names:

names = ["michael", "ron", "scottie", "dennis", "toni"]

The following code:

string = names.inject("") { |x,n| x << "#{n} " }

Will output:

=> "michael ron scottie dennis toni "

You can get the same output with:

string = ""
names.each { |n| string << "#{n} "}

Now, let's say you won't to separate them with comma, and capitalize those that have more then four characters.
You can do it like this:

string = ""
names.each do |n|
  name = n.length > 4 ? n.capitalize : n
  n == names.last ? string << name : string << "#{name}, "
end

Or like this:

string = names.inject("") do |x,n|
  name = n.length > 4 ? n.capitalize : n
  n == names.last ? x << name : x << "#{name}, "
end

In both ways we get the same result:

=> "Michael, ron, Scottie, Dennis, toni"

Technorati Tags: , , ,

9 Responses to “Ruby Inject”

  1. Dmitry 24 April 2008 at 9:57 am Permalink

    I think you have a typo in the last example – the last line before end should have names.last not words.last

  2. vlado 24 April 2008 at 10:31 am Permalink

    Yes it’s a typo. Thanks Dmitry

  3. Noah 1 May 2008 at 4:20 pm Permalink

    You can slightly simply the above idiom:

    # From:
    n == names.last ? string

    Either way, love the ternary use. Ok, bye.

  4. Noah 1 May 2008 at 4:21 pm Permalink

    Oops, it ate my comment, should be:

    # From:
    n == names.last ? string

  5. Noah 1 May 2008 at 4:22 pm Permalink

    Doesn’t like my entity chars?
    # From:
    n == names.last ? string << name : string << “#{name}, ”
    # To:
    string << ((n==names.last) ? name : string + “,”)

  6. Translator 3 July 2008 at 10:52 pm Permalink

    Great article! thanks. And if you like idioms you should check this page outhttp://www.k-international.com/french_idiomsIt gives a list of French idioms with their English translations – really shows how funny language is out of context (and what a nightmare it can be to translate languages!). :-)

  7. Sean 12 August 2008 at 9:40 pm Permalink

    Your last example would be easier to do with map and join:

    string = names.map {|n| n.length > 4 ? n.capitalize : n }.join(“, “)

  8. AlexM 15 August 2008 at 9:18 am Permalink

    Your blog is interesting!

    Keep up the good work!

  9. Mocker 16 October 2009 at 3:05 pm Permalink

    This could be interesting

    sum = nums.inject(0) { |x,n| x+n }

    is equal

    sum = nums.inject(:+)


Leave a Reply