Ruby inject with initial being a hash
Can any one tell me why the following:
['a', 'b'].inject({}) {|m,e| m[e] = e }
throws the error:
IndexError: string not matched
from (irb):11:in `[]='
from (irb):11:in `block in irb_binding'
from (irb):11:in `each'
from (irb):11:in `inject'
from (irb):11
from C:/Ruby192/bin/irb:12:in `<main>'
whereas the following works?
a = {}
a["str"] = "str"
Your block needs to return the accumulating hash:
['a', 'b'].inject({}) {|m,e| m[e] = e; m }
Instead, it's returning the string 'a' after the first pass, which becomes m
in the next pass and you end up calling the string's []=
method.
The block must return the accumulator (the hash), as @Rob said. Some alternatives:
With Hash#update
:
hash = ['a', 'b'].inject({}) { |m, e| m.update(e => e) }
With Enumerable#each_with_object
:
hash = ['a', 'b'].each_with_object({}) { |e, m| m[e] = e }
With Hash#[]
:
hash = Hash[['a', 'b'].map { |e| [e, e] }]
With Array#to_h
(Ruby >= 2.1):
hash = ['a', 'b'].map { |e| [e, e] }.to_h
With Enumerable#mash from Facets:
require 'facets'
hash = ['a', 'b'].mash { |e| [e, e] }
Rather than using inject, you should look into Enumerable#each_with_object
.
Where inject
requires you to return the object being accumulated into, each_with_object
does it automatically.
From the docs:
Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
If no block is given, returns an enumerator.
e.g.:
evens = (1..10).each_with_object([]) {|i, a| a << i*2 }
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
So, closer to your question:
[1] pry(main)> %w[a b].each_with_object({}) { |e,m| m[e] = e }
=> {"a"=>"a", "b"=>"b"}
Notice that inject
and each_with_object
reverse the order of the yielded parameters.