From: Rick DeNatale Date: 2009-09-11T21:21:26+09:00 Subject: Re: populating a hash from an array using inject On Thu, Sep 10, 2009 at 8:42 PM, 7stud -- wrote: > Glenn Jackman wrote: >> I was looking at this problem on Stack Overflow (this one: >> http://stackoverflow.com/questions/1405657/reorganizing-ruby-array-into-hash >> >> The question is how to "convert" an array of objects into a hash. >> >> Consider this code: >> >>     require 'pp' >>     p RUBY_VERSION >> >>     Product = Struct.new(:name, :category) >>     products = [ >>         ['Apple','Golden Delicious'], >>         ['Apple','Granny Smith'], >>         ['Orange','Navel'] >>     ].collect {|cat, name| Product.new(name, cat)} >> >>     foo = products.inject({}) {|h,p| h[p.category] ||= []; h[p.category] >> << p; h} >>     pp foo >> >>     bar = products.inject(Hash.new([])) {|h,p| h[p.category] << p; h} >>     pp bar >> >>     baz = products.inject(Hash.new([])) {|h,p| h[p.category] += p; h} >>     pp baz >> >> It outputs: >> >>     "1.8.7" >>     {"Orange"=>[#], >>      "Apple"=> > > Even though it is not very well written, if you read the documentation > on creating hashes with default values: > > $ ri Hash.new > > -------------------------------------------------------------- Hash::new >     Hash.new                          => hash >     Hash.new(obj)                     => aHash >     Hash.new {|hash, key| block }     => aHash > ------------------------------------------------------------------------ >     Returns a new, empty hash. If this hash is subsequently accessed by >     a key that doesn't correspond to a hash entry, the value returned >     depends on the style of +new+ used to create the hash. In the first >     form, the access returns +nil+. If _obj_ is specified, this single >     object will be used for all _default values_. If a block is >     specified, it will be called with the hash object and the key, and >     should return the default value. It is the block's responsibility >     to store the value in the hash if required. > >        h = Hash.new("Go Fish") >        h["a"] = 100 >        h["b"] = 200 >        h["a"]           #=> 100 >        h["c"]           #=> "Go Fish" >        # The following alters the single default object >        h["c"].upcase!   #=> "GO FISH" >        h["d"]           #=> "GO FISH" >        h.keys           #=> ["a", "b"] > ----------------------------------------------------------- > > > The key line is: > > ***8It is the block's responsibility to store the value in the hash if > required.*** > > Because your block does not assign the array to a hash key, the array is > discarded. The thread has moved on a bit, but. He actually isn't USING the block form of Hash.new, the only blocks are arguments to inject. And Hash.new {|h,k| ... } is what I tend to reach for in a situation like this. foo = products.inject(Hash.new {|h,k| h[k] = []}) {|h,p| h[p.category] << p; h} -- Rick DeNatale Blog: http://talklikeaduck.denhaven2.com/ Twitter: http://twitter.com/RickDeNatale WWR: http://www.workingwithrails.com/person/9021-rick-denatale LinkedIn: http://www.linkedin.com/in/rickdenatale