6

I'm working on learning Ruby, and came across inject. I am on the cusp of understanding it, but when I'm the type of person who needs real world examples to learn something. The most common examples I come across are people using inject to add up the sum of a (1..10) range, which I could care less about. It's an arbitrary example.

What would I use it for in a real program? I'm learning so I can move on to Rails, but I don't have to have a web-centric example. I just need something that has a purpose I can wrap my head around.

Thanks all.

ckbrumb
  • 191
  • 2
  • 11

5 Answers5

7

inject can sometimes be better understood by its "other" name, reduce. It's a function that operates on an Enumerable (iterating through it once) and returns a single value.

There are many interesting ways that it can be used, especially when chained with other Enumerable methods, such as map. Often times, it can be a more concise and expressive way of doing something, even if there is another way to do it.

An example like this may seem useless at first:

range.inject {|sum, x| sum += x}

The variable range, however, doesn't have to be a simple explicit range. It could be (for example) a list of values returned from your database. If you ran a database query that returned a list of prices in a shopping cart, you could use .inject to sum them all and get a total.

In the simple case, you can do this in the SQL query itself. In a more difficult case, such as where some items have tax added to them and some don't, something like inject can be more useful:

cart_total = prices.inject {|sum, x| sum += price_with_tax(x)}

This sort of thing is also particularly useful when the objects in the Enumerable are complex classes that require more detailed processing than a simple numerical value would need, or when the Enumerable contains objects of different types that need to be converted into a common type before processing. Since inject takes a block, you can make the functionality here as complex as you need it to be.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
bta
  • 43,959
  • 6
  • 69
  • 99
  • This may be a matter of taste, but I'd generally do `prices.map(&method(:price_with_tax)).inject(:+)` (so long as the maintainer knows about [&method](http://stackoverflow.com/questions/6962883/how-to-unflatten-a-ruby-array/6963422#6963422)). – Andrew Grimm Aug 21 '12 at 01:18
  • Your example would require `prices.inject(0)`, otherwise it wouldn't calculate the tax on the first item. – Andrew Grimm Aug 21 '12 at 01:19
  • This gives me a bit of a better understanding of inject itself, but I think I'm just too new to Ruby. One big problem, and one which I'll have to remedy, is that I have only ever learned the things I need to know. I've worked with PHP for years - built dozens of sites in it - but all with the same dozen or so functions. I need to focus on really learning Ruby, and not just the basics. When I turn to learn past the basics though, it's all written in tech speak with extreme examples for beginners. I don't want to look up a method and come away scratching my head from the definition and examples. – ckbrumb Aug 21 '12 at 02:26
  • @user1612935- I had the same problem when I first learned Ruby, as I came at it from a similar background. If you learn the language itself (completely separate from its use in web frameworks), you will get a better feeling for the capabilities and style of Ruby. I suggest trying some of the books listed at [ruby-doc.org](http://ruby-doc.org). I found the book "Programming Ruby" to be particularly helpful, even for beginners. – bta Aug 21 '12 at 22:11
6

Here are a couple of inject() examples in action:

[1, 2, 3, 4].inject(0) {|memo, num| memo += num; memo} # sums all elements in array

The example iterates over every element of the [1, 2, 3, 4] array and adds the elements to the memo variable (memo is commonly used as the block variable name). This example explicitly returns memo after every iteration, but the return can also be implicit.

[1, 2, 3, 4].inject(0) {|memo, num| memo += num} # also works

inject() is conceptually similar to the following explicit code:

result = 0
[1, 2, 3, 4].each {|num| result += num}
result # result is now 10

inject() is also useful to create arrays and hashes. Here is how to use inject() to convert [['dogs', 4], ['cats', 3], ['dogs', 7]] to {'dogs' => 11, 'cats' => 3}.

[['dogs', 4], ['cats', 3], ['dogs', 7]].inject({'dogs' => 0, 'cats' => 0}) do |memo, (animal, num)|
  memo[animal] = num
  memo
end

Here is a more generalized and elegant solution:

[['dogs', 4], ['cats', 3], ['dogs', 7]].inject(Hash.new(0)) do |memo, (animal, num)|
  memo[animal] = num
  memo
end

Again, inject() is conceptually similar to the following code:

result = Hash.new(0)
[['dogs', 4], ['cats', 3], ['dogs', 7]].each do |animal, num|
  result[animal] = num
end
result # now equals {'dogs' => 11, 'cats' => 3}
Machavity
  • 30,841
  • 27
  • 92
  • 100
Powers
  • 18,150
  • 10
  • 103
  • 108
0

Instead of a range, imagine you've got a list of sales prices for some item on eBay and you want to know the average price. You can do that by injecting + and then dividing by the length.

sblom
  • 26,911
  • 4
  • 71
  • 95
0

ActiveRecord scopes are a typical case. If we call scoped on a model, we get an object on which we can chain additional scopes. This lets us use inject to build up a search scope from, say, a params hash:

search_params = params.slice("first_name","last_name","city","zip").
  reject {|k,v| v.blank?}

search_scope = search_params.inject(User.scoped) do |memo, (k,v)|
  case k
  when "first_name"
    memo.first_name(v)
  when "last_name"
    memo.last_name(v)
  when "city"
    memo.city(v)
  when "zip"
    memo.zip(v)
  else
    memo
  end

(Note: if NO params are supplied, this brings back the whole table, which might not be what you wanted.)

zetetic
  • 47,184
  • 10
  • 111
  • 119
  • I wish I really understood this, but it's past me right now. I'm not an idiot, I swear I'm not, I just struggle with programming, but I try hard to understand, and once something finally clicks with me, it's there for life. – ckbrumb Aug 21 '12 at 02:29
  • That's OK. There's a lot going on in this example. The point I was trying to make is that `inject` is useful in real-world projects. Think of it as a general function for iterating over a collection and producing a single result--which can be a number, a string, or really any object you want. – zetetic Aug 21 '12 at 02:36
  • That makes more sense. I still don't fully understand it, but I can see that I will likely one day have a need for it and will understand it. – ckbrumb Aug 21 '12 at 02:39
0

My favorite explanation for inject or it's synonym reduce is:

reduce takes in an array and reduces it to a single value. It does this by iterating through a list, keeping and transforming a running total along the way.

I found it in a wonderful article at http://railspikes.com/2008/8/11/understanding-map-and-reduce

ovhaag
  • 1,168
  • 2
  • 9
  • 16