Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Turning a multi-dimensional array into a hash without overwriting values

I have a multi-dimensional array such as:

array = [["stop", "halt"],["stop", "red"],["go", "green"],["go","fast"],["caution","yellow"]]

And I want to turn it into a hash like this:

hash = {"stop" => ["halt","red"], "go" => ["green","fast"], "caution" => "yellow"}

However, when I array.to_h , the values overwrite one another and I get:

hash = {"stop" => "red", "go" => "fast", "caution" => "yellow"}

How do I get the desired array?

like image 394
Garett Arrowood Avatar asked Dec 04 '25 10:12

Garett Arrowood


2 Answers

This is one way. It uses Enumerable#each_with_object and the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged.

array << ["stop", "or I'll fire!"]

array.each_with_object({}) { |(f,l),h|
  h.update(f=>l) { |_,ov,nv| ov.is_a?(Array) ? ov << nv : [ov, nv] } }
  #=> {"stop"=>["halt", "red", "or I'll fire!"],
  #    "go"=>["green", "fast"],
  #    "caution"=>"yellow"}

The code is simplified if you want all values in the returned hash to be arrays (i.e., "caution"=>["yellow"]), which is generally more convenient for subsequent calculations:

array.each_with_object({}) { |(f,l),h|  h.update(f=>[l]) {|_,ov,nv| ov+nv }}
  #=> {"stop"=>["halt", "red", "or I'll fire!"],
  #    "go"=>["green", "fast"],
  #    "caution"=>["yellow"]}
like image 125
Cary Swoveland Avatar answered Dec 07 '25 00:12

Cary Swoveland


One way to do it:

array.inject({}) {|r, (k, v)| r[k] &&= [*r[k], v]; r[k] ||= v; r }

That's pretty messy though. Written out, it looks like this:

def to_hash_with_duplicates(arr)
  {}.tap do |r|
    arr.each do |k, v|
      r[k] &&= [*r[k], v]    # key already present, turn into array and add value
      r[k] ||= v             # key not present, simply store value
    end
  end
end

Edit: Thinking a bit more, @cary-swoveland's update-with-block solution is better, because it handles nil and false values correctly.

like image 31
stoodfarback Avatar answered Dec 07 '25 00:12

stoodfarback