Reputation: 35
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
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
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
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
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