在Ruby中将一个数组的哈希转换为哈希数组

13
我们有以下数据结构:
{:a => ["val1", "val2"], :b => ["valb1", "valb2"], ...}

我想把它变成

[{:a => "val1", :b => "valb1"}, {:a => "val2", :b => "valb2"}, ...]

然后回到第一个表单。有没有人有一个看起来不错的实现?


你是不是指的是{:a => ["val1", "val2", ...], :b => ["valb1", "valb2", ...], ...}?这样输出将包括:c => "valc1",等等。 - DigitalRoss
是的,我们也可以有 :c => ["valc1", "valc2"],然后输出将会在第一个对象中有 :c => "valc1",在第二个对象中有 :c => "valc2"... 此外,每个数组可能不止有两个元素。 - Julien Genestoux
关于笛卡尔积——所有可能组合的结果,请参见计算哈希数组的所有变体 - Phrogz
7个回答

15

以下解决方案适用于任意数量的值 (val1, val2...valN):

{:a => ["val1", "val2"], :b => ["valb1", "valb2"]}.inject([]){|a, (k,vs)| 
  vs.each_with_index{|v,i| (a[i] ||= {})[k] = v} 
  a
}
# => [{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}]

[{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}].inject({}){|a, h| 
  h.each_pair{|k,v| (a[k] ||= []) << v}
  a
}
# => {:a=>["val1", "val2"], :b=>["valb1", "valb2"]}

1
太棒了!这个可以工作 :) 即使哈希/数组中有更多的项 :) 做得好! - Julien Genestoux

8

使用函数式方法(参见Enumerable):

hs = h.values.transpose.map { |vs| h.keys.zip(vs).to_h }
#=> [{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}]

并回到原来的页面:

h_again = hs.first.keys.zip(hs.map(&:values).transpose).to_h
#=> {:a=>["val1", "val2"], :b=>["valb1", "valb2"]}

1

让我们仔细看一下我们试图在之间转换的数据结构:

#Format A
[
 ["val1", "val2"],          :a
 ["valb1", "valb2"],        :b 
 ["valc1", "valc2"]         :c 
]
#Format B
[ :a        :b       :c
 ["val1", "valb1", "valc1"],
 ["val2", "valb2", "valc3"]
]

很容易发现格式B本质上是格式A的转置,因此我们可以提出以下解决方案:

h={:a => ["vala1", "vala2"], :b => ["valb1", "valb2"], :c => ["valc1", "valc2"]}
sorted_keys =  h.keys.sort_by {|a,b| a.to_s <=> b.to_s}

puts sorted_keys.inject([])  {|s,e| s << h[e]}.transpose.inject([])   {|r, a| r << Hash[*sorted_keys.zip(a).flatten]}.inspect
#[{:b=>"valb1", :c=>"valc1", :a=>"vala1"}, {:b=>"valb2", :c=>"valc2", :a=>"vala2"}]

1
m = {}
a,b = Array(h).transpose
b.transpose.map { |y| [a, y].transpose.inject(m) { |m,x| m.merge Hash[*x] }}

1
我的尝试,也许稍微更紧凑一些。
h = { :a => ["val1", "val2"], :b => ["valb1", "valb2"] }

h.values.transpose.map { |s| Hash[h.keys.zip(s)] }

应该在 Ruby 1.9.3 或更新版本中工作。


解释:

首先,将相应的值合并为'行(rows)'

h.values.transpose
# => [["val1", "valb1"], ["val2", "valb2"]] 
< p > 在 map 块中的每次迭代都会产生其中之一:

h.keys.zip(s)
# => [[:a, "val1"], [:b, "valb1"]]

并且Hash[]将它们转换为哈希表:

Hash[h.keys.zip(s)]
# => {:a=>"val1", :b=>"valb1"}      (for each iteration)

0
你可以使用 inject 来构建哈希数组。
hash = { :a => ["val1", "val2"], :b => ["valb1", "valb2"] }
array = hash.inject([]) do |pairs, pair|
  pairs << { pair[0] => pair[1] }
  pairs
end
array.inspect # => "[{:a=>["val1", "val2"]}, {:b=>["valb1", "valb2"]}]"

Ruby文档中有更多的示例关于使用inject


谢谢...但抱歉,这在哈希和数组中使用更多项时无法工作。 - Julien Genestoux

0
这将适用于假设原始哈希中的所有数组大小相同的情况:
hash_array = hash.first[1].map { {} }
hash.each do |key,arr|
  hash_array.zip(arr).each {|inner_hash, val| inner_hash[key] = val}
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接