user1249263
user1249263

Reputation: 35

Ruby how does this inject code work?

I am new to Ruby and I am trying to write a method that groups an array of words into anagram groups. Here is the code:

def combine_anagrams(words)
  dict = words.inject(Hash.new(0)) do |list,ws|
    key = sort_word(ws)
    if !list.has_key?(key)
      list[key] = []
    end
    list[key].push(ws)
    list             #What is this
  end
  return dict.values
end   

My question is what the statement list is for. If I take it out list becomes an array instead of hash.

Upvotes: 1

Views: 1264

Answers (4)

umar
umar

Reputation: 4389

the statement "list" is the return value of the whole block. The line: "list[key] = []" has a return value of "list", therefore it doesnt need another line to set the return value of the if condition to 'list', but the return value of list[key].push(ws) is list[key]. we want to get the updated value of list in the end, therefore we need to return that value from the block each time, so that further processing acts of the updated list, and not something else.

As a background, each ruby line also has a return value, so if that were the last line of a block, or a function, it automatically becomes the return value of the whole block or the function respectively.

To understand this further, try some code like this in irb:

a = [1,2,3,4,5]
b = a.inject(0) {|sum, val| puts sum; puts val; sum + val}

the inner block comprises of three statememts; the last statement returns the value of sum+val to the block, which get stored in sum, to be used in next iterations.

Also, try some code like this:

h = {:a => []}
b = h[:a].push 6

See what b evaluates to; in your code, you need 'b' to be the accumulated hash, and not the array that is stored in h[:a]

Upvotes: 0

Sergey
Sergey

Reputation: 11908

Take a look at http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-inject

In the inject method if you pass two arguments into it (in your case list and ws) the first one - list - is so-called accumulator value. The value which is returned by the inject block at each iteration step is assigned to the list variable. So the line with the only word "list" which you commented as "#What is this" is used for assigning the value of the list in the block to the "list" accumulator variable.

Upvotes: 0

Michelle Tilley
Michelle Tilley

Reputation: 159105

inject works like this:

final = enumerable.inject(initial_value) do |current_value, iteration|
  # calculations, etc. here
  value # next iteration, current_value will be whatever the block returns
end

So, in your case, initial_value is Hash.new(0), or an empty Hash with 0 as the default value for a key that doesn't exist instead of nil. This is passed into the inject block for the first element in enumerable.

Inside the inject block, you check to see if key already exists as a key on the hash. If it does not, set it equal to an empty array. In either case, take the current iteration of words (ws) and push it onto the array.

Finally, the block returns the current version of list; it becomes current_value (the first parameter to the inject block) the next time the loop processes an element from enumerable.

As a more simple example, check out this sample:

numbers = [1, 2, 3, 4]
sum = inject(0) do |total, number| # first time, total will be 0
  total + number # set total next time to be whatever total is now plus the current number
end

Upvotes: 1

Andrew Marshall
Andrew Marshall

Reputation: 96914

Every method/block/etc. in Ruby returns something, and unless there is an early return statement, whatever the last statement in the method/block/etc. is, is what is returned.

In your case, having list be the last line in the block passed to inject ensures that list is returned by the block. When you remove it, the return value of list[key].push(ws) is returned, which obviously isn't what you want.

Note that this behavior also makes using the return keyword when it is the last statement that would be executed otherwise is unnecessary (this includes the return you have at the end of your method). Though some prefer to be explicit that they intend to return something and use them even when not needed.

On an unrelated note: your if !list.has_key?(key) can be rewritten unless list.has_key?(key).

Upvotes: 1

Related Questions