From: Glenn Jackman on
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"=>[#<struct Product name="Navel", category="Orange">],
"Apple"=>
[#<struct Product name="Golden Delicious", category="Apple">,
#<struct Product name="Granny Smith", category="Apple">]}
{}
{"Orange"=>[#<struct Product name="Navel", category="Orange">],
"Apple"=>
[#<struct Product name="Golden Delicious", category="Apple">,
#<struct Product name="Granny Smith", category="Apple">]}

My question: why is bar empty?

--
Glenn Jackman
Write a wise saying and your name will live forever. -- Anonymous
From: Ehsanul Hoque on

I don't have an answer, but trying out the code in JRuby, I can see that the default value for bar becomes simply the products array after the inject() method is used. One more thing that requires an explanation. You can see this by appending this line to your code:

pp bar["random string here"]


> Date: Fri, 11 Sep 2009 08:30:07 +0900
> From: glennj(a)ncf.ca
> Subject: populating a hash from an array using inject
> To: ruby-talk(a)ruby-lang.org
>
> 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"=>[#<struct Product name="Navel", category="Orange">],
> "Apple"=>
> [#<struct Product name="Golden Delicious", category="Apple">,
> #<struct Product name="Granny Smith", category="Apple">]}
> {}
> {"Orange"=>[#<struct Product name="Navel", category="Orange">],
> "Apple"=>
> [#<struct Product name="Golden Delicious", category="Apple">,
> #<struct Product name="Granny Smith", category="Apple">]}
>
> My question: why is bar empty?
>
> --
> Glenn Jackman
> Write a wise saying and your name will live forever. -- Anonymous
>

_________________________________________________________________
Windows Live: Make it easier for your friends to see what you’re up to on Facebook.
http://windowslive.com/Campaign/SocialNetworking?ocid=PID23285::T:WLMTAGL:ON:WL:en-US:SI_SB_facebook:082009
From: 7stud -- on
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"=>[#<struct Product name="Navel", category="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.




--
Posted via http://www.ruby-forum.com/.

From: 7stud -- on
Sorry, I pasted the ri output on top of the first part of my post, which
said something to the effect of:

> My question: why is bar empty?

...because h[non_existent_key] sends an array that is unassociated with
any hash to the block. In other words, creating a hash with a default
value does not cause a key/value pair to be created in the hash when a
non-existent key is accessed.



--
Posted via http://www.ruby-forum.com/.

From: 7stud -- on
7stud -- wrote:
> Sorry, I pasted the ri output on top of the first part of my post, which
> said something to the effect of:
>
>> My question: why is bar empty?
>
> ...because h[non_existent_key] sends an array that is unassociated with
> any hash to the block. In other words, creating a hash with a default
> value does not cause a key/value pair to be created in the hash when a
> non-existent key is accessed.

It was probably best that first sentence was erased. Here is what I was
trying to say:

h = Hash.new([])

result = h["A"]
p result

--output:--
[]

p h

--output:--
{}

When you write:

h[p.category] << p

The example above demonstrates that is equivalent* to:

[] << p

...and that simply appends an object to an empty array, and does nothing
to the hash.

And you are in even worse shape than you realize. If you take the empty
array that is returned as the default value and append an object to the
array, and then manually assign the array to a key in the hash:

h = Hash.new([])

h["A"] = h["A"] << 10 #==>[10]

h["B"] = h["B"] << 20 #==>[20]

Look what happens:


p h

{"A"=>[10, 20], "B"=>[10, 20]}

Yep, ruby hands you a reference to the same array over and over again.







--
Posted via http://www.ruby-forum.com/.