I tried to implement reduce using curly braces, but was unable to get it to work. The function is supposed to take an array of words (symbols or strings) and create a hash where the keys are the words and the values are the length of the words. Here is my curly braces attempt:
def find_word_lengths(word_list)
word_list.reduce(Hash.new(0)) { |hash,word| hash[word] = word.length}
end
I keep getting the error undefined method '[]=' for 3:Integer
.
When I implement it with do
instead of curly braces, it works, but only when I return the accumulator at the end of the reduce block.
def find_word_lengths(word_list)
word_list.reduce(Hash.new(0)) do |hash,word|
hash[word] = word.length
hash
end
end
I can see why the second one works, but I don't understand why the first one doesn't. My understanding of reduce is that it returns the accumulator implicitly after running through the array it's called on. At least, when I write a reduce function call that reduces to a number, it automatically returns the sum
my_numbers = [5, 6, 7, 8]
my_numbers.reduce(1000) { |sum, number| sum number }
#=>1026
Does reduce behave differently depending on the type of the accumulator?
CodePudding user response:
The accumulator is NOT returned implicitly. This is the assumption which breaks the rest of your analysis.
The is stated in the docs if you read carefully:
... In either case, the result becomes the new value for memo. ..
What this is talking about is the result of the execution of the block, whether curly braces or do/end, becomes the new value for memo on the next round.
So both curly braces and do/end work exactly the same, it was just that you assumed the memo was returned implicitly, when it isn't.
If you analyze your code now with this new knowledge, you should hopfully understand why some of the examples worked while others did not.
Remember, the last statement inside the block will be the value of the memo on the next iteration.
CodePudding user response:
In your first example, you return the result of assigning word.length
to the hash[word]
, i.e. word.length
itself. Thus, the seciond iteration of the loop will retrieve the length of the first word of your word_list
as its argument.
In your second example, you are explicitly return the hash in the block (which then works).
An equivalent one-liner version with curly braces to your working multi-line version is
word_list.reduce(Hash.new(0)) { |hash, word| hash[word] = word.length; hash }
Instead of using reduce
, you could also use each_with_object
instead which will always yield the original object to each iteration:
word_list.each_with_object(Hash.new(0)) { |hash, word| hash[word] = word.length }