Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert array of 2-element arrays into a hash, where duplicate keys append additional values

Tags:

arrays

ruby

hash

For example

Given an array:

array = [[:a,:b],[:a,:c],[:c,:b]] 

Return the following hash:

hash = { :a => [:b,:c] , :c => [:b] } 

hash = Hash[array] overwrites previous associations, producing:

hash = { :a => :c , :c => :b } 
like image 563
smallsense Avatar asked Feb 14 '12 02:02

smallsense


People also ask

How do you turn an array into a hash in Ruby?

The to_h method is defined in the array class. It works to convert an array to a hash in the form of key-value pairs. The method converts each nested array into key-value pairs. The method also accepts a block.

Can array have two same keys?

No, you cannot have multiple of the same key in an associative array. You could, however, have unique keys each of whose corresponding values are arrays, and those arrays have multiple elements for each key.

Can a PHP array have duplicate keys?

Code Inspection: Duplicate array keysReports duplicate keys in array declarations. If multiple elements in the array declaration use the same key, only the last one will be used, and all others will be overwritten.

What does TO_H do in Ruby?

Ruby | Array to_h() function Array#to_h() : to_h() is a Array class method which returns the result of interpreting ary as an array of [key, value] pairs. Return: the result of interpreting ary as an array of [key, value] pairs.


2 Answers

Using functional baby steps:

irb:01.0> array = [[:a,:b],[:a,:c],[:c,:b]] #=> [[:a, :b], [:a, :c], [:c, :b]]  irb:02.0> array.group_by(&:first) #=> {:a=>[[:a, :b], [:a, :c]], :c=>[[:c, :b]]}  irb:03.0> array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } #=> [[:a, [:b, :c]], [:c, [:b]]]  irb:04.0> Hash[ array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } ] #=> {:a=>[:b, :c], :c=>[:b]} 

Using imperative style programming:

irb:10.0> h = Hash.new{ |h,k| h[k]=[] } #=> {}  irb:11.0> array.each{ |k,v| h[k] << v } #=> [[:a, :b], [:a, :c], [:c, :b]]  irb:12.0> h #=> {:a=>[:b, :c], :c=>[:b]} 

As an imperative one-liner:

irb:13.0> h = Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } } #=> {:a=>[:b, :c], :c=>[:b]} 

Or using everyone's favorite inject:

irb:14.0> array.inject(Hash.new{ |h,k| h[k]=[] }){ |h,(k,v)| h[k] << v; h } #=> {:a=>[:b, :c], :c=>[:b]} 

If you really want to have single values not collided as an array, you can either un-array them as a post-processing step, or use a different hash accumulation strategy that only creates an array upon collision. Alternatively, wrap your head around this:

irb:17.0> hashes = array.map{ |pair| Hash[*pair] } # merge many mini hashes #=> [{:a=>:b}, {:a=>:c}, {:c=>:b}]  irb:18.0> hashes.inject{ |h1,h2| h1.merge(h2){ |*a| a[1,2] } } #=> {:a=>[:b, :c], :c=>:b} 
like image 81
Phrogz Avatar answered Oct 05 '22 15:10

Phrogz


EDIT: In Ruby 2.1+, you can use Array#to_h

pry(main)> [[:a,:b],[:a,:c],[:c,:b]].to_h => {:a=>:c, :c=>:b} 

END EDIT

The public [] method on the Hash class accepts a key-value pair array and returns a hash with the first element of the array as key and the second as value.

The last value in the key-value pair will be the actual value when there are key duplicates.

Hash[[[:a,:b],[:a,:c],[:c,:b]]]     => {:a=>:c, :c=>:b} 

This syntax is valid in 1.9.3+ ; I'm not sure about earlier Ruby versions (it's not valid in 1.8.7)

ref: http://www.ruby-doc.org/core-2.1.0/Hash.html#method-c-5B-5D

Another interesting way of doing it would be using the inject method: (obviously the method above is more succinct and recommended for this specific problem)

[ [:a, :b], [:a, :c], [:c, :b] ].inject({}) { |memo, obj|     memo[obj.first] = obj.last    memo  }  => {:a=>:c, :c=>:b} 

inject iterates over the enumerable, your array in this case, starting with the injected parameter, in this case the empty hash {}.

For each object in the enumerable, the block is called with the variables memo and obj:

  • obj is the current object in the array

  • memo is the value that has been returned by your block's last iteration (for the first iteration, it's what you inject)

like image 25
Abdo Avatar answered Oct 05 '22 15:10

Abdo