a = [[1, 'a'],[2, 'b'],[3, 'c'], [4, 'd']] a.inject({}) {|r, val| r[val[0]] = val[1]}
When I run this, I get an index error
When I change the block to
a.inject({}) {|r, val| r[val[0]] = val[1]; r}
It then works.
How is ruby handling the first inject attempt that isn't getting what I want?
Is there a better way to do this?
Just because Ruby is dynamically and implicitly typed doesn't mean that you don't have to think about types.
The type of Enumerable#inject
without an explicit accumulator (this is usually called reduce
) is something like
reduce :: [a] → (a → a → a) → a
or in a more Rubyish notation I just made up
Enumerable[A]#inject {|A, A| A } → A
You will notice that all the types are the same. The element type of the Enumerable
, the two argument types of the block, the return type of the block and the return type of the overall method.
The type of Enumerable#inject
with an explicit accumulator (this is usually called fold
) is something like
fold :: [b] → a → (a → b → a) → a
or
Enumerable[B]#inject(A) {|A, B| A } → A
Here you see that the accumulator can have a different type than the element type of the collection.
These two rules generally get you through all Enumerable#inject
-related type problems:
In this case, it is Rule #1 that bites you. When you do something like
acc[key] = value
in your block, assignments evaluate to the assigned value, not the receiver of the assignment. You'll have to replace this with
acc.tap { acc[key] = value }
See also Why Ruby inject method cannot sum up string lengths without initial value?
BTW: you can use destructuring bind to make your code much more readable:
a.inject({}) {|r, (key, value)| r[key] = value; r }
There is an easier way -
a = [[1, 'a'],[2, 'b'],[3, 'c'], [4, 'd']]
b = Hash[a] # {1=>"a", 2=>"b", 3=>"c", 4=>"d"}
The reason the first method isn't working, is because inject uses the result of the block as the r
in the next iteration. For the first iteration, r
is set to the argument you pass to it, which in this case is {}
.
The first block returns the result of the assignment back into the inject
, the second returns the hash, so it actually works.
A lot of people consider this usage of inject
an anti-pattern; consider each_with_object
instead .
a.inject({}) { |r, val| r.merge({ val[0] => val[1] }) }
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.