Home > Net >  Ruby hash with multiple comma separated values to array of hashes with same keys
Ruby hash with multiple comma separated values to array of hashes with same keys

Time:02-05

What is the most efficient and pretty way to map this:

{name:"cheese,test", uid:"1,2"}

to this:

[ {name:"cheese", uid:"1"},  {name:"test", uid:"2"} ]

should work dinamically for example with: { name:"cheese,test,third", uid:"1,2,3" } or {name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }

Finally I made this:

hash = {name:"cheese,test", uid:"1,2"}
results = []
length = hash.values.first.split(',').length
length.times do |i|
   results << hash.map {|k,v| [k, v.split(',')[i]]}
end
results.map{|e| e.to_h}

It is working, but i am not pleased with it, has to be a cleaner and more 'rubyst' way to do this

CodePudding user response:

def splithash(h)
  # Transform each element in the Hash...
  h.map do |k, v|
    # ...by splitting the values on commas...
    v.split(',').map do |vv|
      # ...and turning these into individual { k => v } entries.
      { k => vv }
    end
  end.inject do |a,b|
    # Then combine these by "zip" combining each list A to each list B...
    a.zip(b)

    # ...which will require a subsequent .flatten to eliminate nesting
    # [ [ 1, 2 ], 3 ] -> [ 1, 2, 3 ] 
  end.map(&:flatten).map do |s|
    # Then combine all of these { k => v } hashes into one containing
    # all the keys with associated values.
    s.inject(&:merge)
  end
end

Which can be used like this:

splithash(name:"cheese,test", uid:"1,2", example:"a,b")
# => [{:name=>"cheese", :uid=>"1", :example=>"a"}, {:name=>"test", :uid=>"2", :example=>"b"}]

It looks a lot more convoluted at first glance, but this handles any number of keys.

CodePudding user response:

I would likely use transpose and zip like so:

hash = {name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }

hash.values.map{|x| x.split(",")}.transpose.map{|v| hash.keys.zip(v).to_h}

#=> [{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]

To break it down a bit (code slightly modified for operational clarity):

hash.values  
#=>  ["cheese,test,third,fourth", "1,2,3,4", "9,8,7,6"]
.map{|x| x.split(",")}  
#=>  [["cheese", "test", "third", "fourth"], ["1", "2", "3", "4"], ["9", "8", "7", "6"]]
.transpose  
#=>  [["cheese", "1", "9"], ["test", "2", "8"], ["third", "3", "7"], ["fourth", "4", "6"]]
.map do |v| 
  hash.keys  #=>  [[:name, :uid, :age], [:name, :uid, :age], [:name, :uid, :age], [:name, :uid, :age]]
  .zip(v)  #=>  [[[:name, "cheese"], [:uid, "1"], [:age, "9"]], [[:name, "test"], [:uid, "2"], [:age, "8"]], [[:name, "third"], [:uid, "3"], [:age, "7"]], [[:name, "fourth"], [:uid, "4"], [:age, "6"]]]
  .to_h  #=>  [{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
end

CodePudding user response:

Input

hash={name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }

Code

p hash
    .transform_values { |v| v.split(',') }
    .map { |k, v_arr| v_arr.map { |v| [k, v] }
    }
    .transpose
    .map { |array| array.to_h }

Output

[{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]

CodePudding user response:

We are given

h = { name: "cheese,test", uid: "1,2" }

Here are two ways to create the desired array. Neither construct arrays that are then converted to hashes.

#1

First compute

g = h.transform_values { |s| s.split(',') }
  #=> {:name=>["cheese", "test"], :uid=>["1", "2"]}

then compute

g.first.last.size.times.map { |i| g.transform_values { |v| v[i] } }
  #=> [{:name=>"cheese", :uid=>"1"}, {:name=>"test", :uid=>"2"}]

Note

a = g.first
  #=> [:name, ["cheese", "test"]]
b = a.last
  #=> ["cheese", "test"]
b.size
  #=> 2

#2

This approach does not convert the values of the hash to arrays.

(h.first.last.count(',') 1).times.map do |i|
  h.transform_values { |s| s[/(?:\w ,){#{i}}\K\w /] }
end
  #=> [{:name=>"cheese", :uid=>"1"}, {:name=>"test", :uid=>"2"}]

We have

a = h.first
  #=> [:name, "cheese,test"]
s = a.last
  #=> "cheese,test"
s.count(',') 1
  #=> 2

We can express the regular expression in free-spacing mode to make it self-documenting.

/
(?:     # begin a non-capture group
  \w ,  # match one or more word characters followed by a comma  
)       # end the non-capture group
{#{i}}  # execute the preceding non-capture group i times
\K      # discard all matches so far and reset the start of the match
\w      # match one or more word characters
/x      # invoke free-spacing regex definition mode
  •  Tags:  
  • ruby
  • Related