3

I have hash, all its values are arrays, like this:

list = { letter:  ['a', 'b', 'c'],
         number:  ['one', 'two', 'three'],
         fruit:   ['apple', 'pear', 'kiwi'],
         car:     ['vw', 'mb', 'bmw'],
         state:   ['la', 'ny', 'fl'],
         color:   ['red', 'white', 'black'],
         tree:    ['oak', 'pine', 'maple'],
         animal:  ['cat', 'dog', 'rat'],
         clothes: ['tie', 'sock', 'glove'] }

In fact this hash could have more keys and values could be larger, but always sizes of every value are the same (in this case - three).

I want to convert this hash to array of hashes.

Each hash will have all keys of origin hash and respective value.

So finally I want to have:

list = [
  { letter: 'a', number: 'one', fruit: 'apple', car: 'vw', state: 'la',
    color: 'red', tree: 'oak', animal: 'cat', clothes: 'tie' },

  { letter: 'b', number: 'two', fruit: 'pear', car: 'mb', state: 'ny',
    color: 'white', tree: 'pine', animal: 'dog', clothes: 'sock' },

  { letter: 'c', number: 'three', fruit: 'kiwi', car: 'bmw', state: 'fl',
    color: 'black', tree: 'elm', animal: 'rat', clothes: 'glove' }
]

What's the best way to do it?

mechnicov
  • 12,025
  • 4
  • 33
  • 56

6 Answers6

9
[list.keys].product(list.values.transpose).map { |a| a.transpose.to_h }
   #=> [{:letter=>"a", :number=>"one", :fruit=>"apple", :car=>"vw", 
   #     :state=>"la", :color=>"red", :tree=>"oak", :animal=>"cat",
   #     :clothes=>"tie"},
   #    {:letter=>"b", :number=>"two", :fruit=>"pear", :car=>"mb",
   #     :state=>"ny", :color=>"white", :tree=>"pine", :animal=>"dog",
   #     :clothes=>"sock"},
   #    {:letter=>"c", :number=>"three", :fruit=>"kiwi", :car=>"bmw",
   #     :state=>"fl", :color=>"black", :tree=>"maple", :animal=>"rat", 
   #     :clothes=>"glove"}]

Suppose list were defined as follows.

list = {
  letter:  ['a',     'b'   ],
  number:  ['one',   'two' ],
  fruit:   ['apple', 'pear'],
  car:     ['vw',    'mb'  ]
}

The steps would be as follows.

b = [list.keys]
  #=> [[:letter, :number, :fruit, :car]]
c = list.values
  #=> [["a", "b"], ["one", "two"], ["apple", "pear"], ["vw", "mb"]]
d = c.transpose
  #=> [["a", "one", "apple", "vw"],
  #    ["b", "two", "pear",  "mb"]]
e = b.product(d)
  #=> [[[:letter, :number, :fruit, :car], ["a", "one", "apple", "vw"]],
  #    [[:letter, :number, :fruit, :car], ["b", "two", "pear",  "mb"]]]
e.map { |a| a.transpose.to_h }
  #=> [{:letter=>"a", :number=>"one", :fruit=>"apple", :car=>"vw"},
  #    {:letter=>"b", :number=>"two", :fruit=>"pear",  :car=>"mb"}]

Let's look at the last step more closely. map passes the first element of e to the block and sets the block variable a to its value:

a = e.first
  #=> [[:letter, :number,  :fruit,  :car],
  #    ["a",     "one",    "apple", "vw"]]

The block calculation is as follows.

f = a.transpose
  #=> [[:letter, "a"], [:number, "one"], [:fruit, "apple"], [:car,   "vw"]]
f.to_h
  #=> {:letter=>"a", :number=>"one", :fruit=>"apple", :car=>"vw"}

The remaining calculations for e.map { |a| a.transpose.to_h } are similar.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • This is similar to @Marcin's answer. The main difference is that I used [Array#product](http://ruby-doc.org/core-2.5.1/Array.html#method-i-product) and used [Array#transpose](http://ruby-doc.org/core-2.5.1/Array.html#method-i-transpose) rather than [Enumerable#zip](http://ruby-doc.org/core-2.5.1/Enumerable.html#method-i-zip), the latter being yin and yang. – Cary Swoveland Dec 23 '18 at 18:30
  • @mechnicov, thanks for the compliment and also for mentioning the problem with the mixed-up lists. I fixed that. – Cary Swoveland Dec 24 '18 at 17:43
5

Leveraging Array#transpose and Array#to_h

keys = list.keys
list.values.transpose.map { |v| keys.zip(v).to_h }
Marcin Kołodziej
  • 5,253
  • 1
  • 10
  • 17
3

All in one:

list.map{|k,v| [k].product(v)}.transpose.map(&:to_h)

The idea is to use each key product its values, map out, transpose, then convert to hash by to_h.

Til
  • 5,150
  • 13
  • 26
  • 34
2

Try following code,

arr = []
3.times { |x| arr[x] = {}; list.each { |k,v| arr[x][k] = v[x] } }
arr.inspect

Output will be

=> [{:letter=>"a", :number=>"one", :fruit=>"apple", :car=>"vw", :state=>"la", :color=>"red", :tree=>"oak", :animal=>"cat", :clothes=>"tie"}, {:letter=>"b", :number=>"two", :fruit=>"pear", :car=>"mb", :state=>"ny", :color=>"white", :tree=>"pine", :animal=>"dog", :clothes=>"sock"}, {:letter=>"c", :number=>"three", :fruit=>"kiwi", :car=>"bmw", :state=>"fl", :color=>"black", :tree=>"maple", :animal=>"rat", :clothes=>"glove"}]
ray
  • 5,454
  • 1
  • 18
  • 40
0

Just to play with Enumerable#each_with_object:

list.values.transpose.each_with_object([]) { |v, a| a << v.each_with_index.with_object({}) { |(vv, i), h| h[list.keys[i]] = vv } }
iGian
  • 11,023
  • 3
  • 21
  • 36
0

Using Array::new as new array "builder" and Enumerable#each_with_object as every item "builder"

Array.new(list.first.last.size) do |index|
  list.each_with_object({}) { |(key, values), new_item| new_item[key] = values[index] }
end
mechnicov
  • 12,025
  • 4
  • 33
  • 56