Topic: hash key with multiple values

Hi ,
i have the hash result is

puts @results
[{x=>"i8"}, {y=>"i58"}, {y=>"i6"}, {y=>"v5"}, {z=>"ci5"}, {z=>"i63"},  {w=>"cie201"}]

from this @results hash  ,i want to get new hash like below

puts @conver_results
[{x=>"i8"}, {y=>"i58","i6","v5"}, {z=>"ci5","i63"},  {w=>"cie201"}]

Please help me

Re: hash key with multiple values

I think your question is purely a Ruby question, try posting to a Ruby only forum, or searching a purely Ruby forum,  and you'd get your answer much quicker.

But I'll take a stab:

In my Ruby book (The Ruby Way, 2nd Edition, Hal Fulton) is a section called:

Implementing a hash with duplicate keys

It's a daunting read,  but therein lies your answer I feel.

Joe got a job, on the day shift, at the Utility Muffin Research Kitchen, arrogantly twisting the sterile canvas snout of a fully charged icing anointment utensil.

Re: hash key with multiple values

Your first example isn't a hash, it's an array of hashes, each with one entry, which is why it seems to allow duplicates. I'll start with your data (I've used the 1.9 syntax for symbols in hash literals, this won't work at 1.8x, but you already have the data so it shouldn't matter).

array_of_hash = [{x: "i8"}, {y: "i58"}, {y: "i6"}, {y: "v5"}, {z: "ci5"}, {z: "i63"},  {w: "cie201"}]

hash_of_array = Hash.new { |hash, key| hash[key] = [] } #(1)

array_of_hash.each do |entry| #(2)                             
  entry.each_pair { |key, value| hash_of_array[key] << value } #(3)
end

p hash_of_array

  1. We pass a block to the Hash.new method; this overrides the default value for undefined keys from nil to an empty array.

  2. Then we iterate over all the entries in the original array; each one is a hash.

  3. Then we iterate over all the key-value pairs in the hash, using the key to push the values onto the value array indexed by that key in the output hash_of_arrays. In your data, there is only one entry in each hash, but this doesn't matter - it allows us to be more flexible about processing empty hashes

If you need to use this kind of construct often, it might be worth making it a class in its own right, and overriding the methods to make them easier to use and hide the underlying implementation, but for now it will do what you want.

Note that it accepts duplicates inside the arrays e.g. :x => [1, 1, 2] is allowed, which may or may not be what you want...

Re: hash key with multiple values

Posting a ruby question in the ruby section of rails forum is totally legitimate isn't it?

First of all, you can't have this:
[{x=>"i8"}, {y=>"i58","i6","v5"}, {z=>"ci5","i63"},  {w=>"cie201"}]

as the syntax is wrong.  It would need to be

[{x=>"i8"}, {y=>["i58","i6","v5"]}, {z=>["ci5","i63"]},  {w=>"cie201"}]

or

[{x=>["i8"]}, {y=>["i58","i6","v5"]}, {z=>["ci5","i63"]},  {w=>["cie201"]}]

Personally i think the latter is better as it's more consistent.

Also, you're using variable names as the keys here, i'd expect them to be symbols or strings.  Let's assume they're supposed to be strings, so we start with

array = [{"x"=>"i8"}, {"y"=>"i58"}, {"y"=>"i6"}, {"y"=>"v5"}, {"z"=>"ci5"}, {"z"=>"i63"},  {"w"=>"cie201"}]

I would do it like this.  It's on more lines than it could be but i've gone for readability over terseness.

array = [{"x"=>"i8"}, {"y"=>"i58"}, {"y"=>"i6"}, {"y"=>"v5"}, {"z"=>"ci5"}, {"z"=>"i63"},  {"w"=>"cie201"}]
array.each do |hash|
   key = hash.keys.first
   value = hash[key]
   if result.has_key?(key)
     result[key] << value
   else
     result[key] = [value]
   end
end

>>result
=> {"w"=>["cie201"], "x"=>["i8"], "y"=>["i58", "i6", "v5"], "z"=>["ci5", "i63"]}

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: hash key with multiple values

Max Williams wrote:

It's on more lines than it could be but i've gone for readability over terseness.

Oh dear. And there was me thinking my code was all spare and elegant, rather than terse and unreadable. I may have to flounce off in a huff...

Re: hash key with multiple values

keep hold of your handbag specious, i wasn't having a go at your code.  This

Hash.new { |hash, key| hash[key] = [] }

is cool, i didn't know you could do this.

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: hash key with multiple values

Just kidding Max, I'm not that easily offended smile (although you needn't treat that as a challenge...)

I initially tried with just

Hash.new([])

but this didn't work - it seems to store the default empty array with the hash, but almost like a singleton, so they all get the same one. Although this is OK for ensuring that you get an empty list back if you try to read a non-existent key, you can't assign to it because the key doesn't actually point to a real array. By using the block as the default value, each time you access the hash with a key that is undefined, it makes a new empty array and assigns it to the key. Which means you can just do the push onto the array directly without worrying if it exists already or not.

I ran across this trick when I was trying to make a nested data structure in Ruby in the same way I would in Perl, where I can just

$a{$b}->[$c]->{$d}++;

and Perl will create all the intermediate data structures if they don't already exist, a process called autovivification.

Re: hash key with multiple values

yeah, that's cool.  Just had a bit of a mad thought.  It would be nice to have a hash that you could call nested keys on without it blowing up.  eg

#normal situation
hash = {:one => "bun"}
hash[:two]
=> nil
hash[:two][:three]
=> exception

#desired situation
hash = {:one => "bun"}
hash[:two]
=> nil
hash[:two][:three]
=> nil

So, this:
hash = Hash.new { |hash, key| hash[key] = {} }

gives you one level of protection, if you like:

hash = {:one => "bun"}
hash[:two]
=> nil
hash[:two][:three]
=> nil

However it doesn't go any further:

hash[:two][:three][:four]
=> exception

Changing it to the following bit of madness...

hash = Hash.new { |hash, key| hash[key] = Hash.new { |hash2, key2| hash2[key2] = {} }  }

now gives you two levels of protection (this is starting to sound like a shaving cream or something):

hash[:two][:three][:four]
=> nil

hash[:two][:three][:four][:five]
=> exception

Is there a nice way you know of to have a hash that can be called with infinitely nested keys without blowing up, and instead just returning nil?

Last edited by Max Williams (2010-04-09 08:12:49)

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: hash key with multiple values

Yes, there is, and I posted it here a while back. It's a classic piece of Ruby (I can say this because I didn't write it, massive kudos to Daniel Martin, who did), and it took me some time to understand what it was doing. It uses the same idea, but takes it a step further by passing the default_proc of the current hash down to the child hash constructor, so it works down to any depth. Which is actually very cool...

http://railsforum.com/viewtopic.php?id=35726

Re: hash key with multiple values

specious wrote:

Yes, there is, and I posted it here a while back. It's a classic piece of Ruby (I can say this because I didn't write it, massive kudos to Daniel Martin, who did), and it took me some time to understand what it was doing. It uses the same idea, but takes it a step further by passing the default_proc of the current hash down to the child hash constructor, so it works down to any depth. Which is actually very cool...

http://railsforum.com/viewtopic.php?id=35726

That is *fracking* cool.   It's a little bit different from my example as it returns an empty hash instead of nil for an unknown key, but if you amend all the if tests that use it to say (for example)

  if !options[:foo][:bar].blank?

instead of

  if options[:foo][:bar]

then it's very useable.

What i want to work out now is how to make that the default behaviour for hashes in my rails app....

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: hash key with multiple values

This prompted me to think about the downside of Perl's autovivification - it's a variant on Schrodinger's cat, because you don't know if it exists before you check for it, but as soon as you check for it, it appears. The way round this in Perl is to use the exists function which checks for the existence of a key without materialising it and setting the value to undef. Run the following test to see what I mean

my_hash = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }

my_hash[:a][:b][:c] = true

p my_hash

puts 'Not found' unless (my_hash[:a][:b].key?(:d))

p my_hash

puts 'Not found with side effects' if (my_hash[:a][:b][:d].empty?)

p my_hash

Note that the first test leaves the structure unchanged while the second actually adds a key and an empty hash, which is an interesting side effect for what looks like a read-only action.

The first test won't stop it auto-creating the intermediate levels, but could be useful if you know how deep the hashes will nest and you only want to test the leaf nodes.

I tend to use this kind of structure when I'm reading unsorted raw data and I want to apply some kind of classification, summarisation, or counts to it, so the resulting structure gets built and shaped from the data content. But using it to hold options is another good application for it.

Re: hash key with multiple values

yeah, that makes sense - it becomes like a multidimensional array in that you can query any point in your data space and either get a value or 'empty' which in this case is the empty hash, without worrying about it blowing up.  cool stuff.

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams