【问题标题】:How can I transpose array of hashes in ruby如何在 ruby​​ 中转置哈希数组
【发布时间】:2020-03-24 02:53:35
【问题描述】:

我有以下哈希数组作为输入:-

input =[
{"ID"=>"100", "Key"=>"Field A", "Value"=>"123"}, 
{"ID"=>"100", "Key"=>"Field B", "Value"=>"333"}, 
{"ID"=>"100", "Key"=>"Field C", "Value"=>"555"}, 
{"ID"=>"200", "Key"=>"Field A", "Value"=>"789"}, 
{"ID"=>"200", "Key"=>"Field B", "Value"=>"999"},
{"ID"=>"200", "Key"=>"Field D", "Value"=>"444"}
]

我想将这个哈希数组转换如下

output =[
{"ID"=>"100", "Field A"=>"123", "Field B"=>"333", "Field C" => "555", "Field D" => ""}, 
{"ID"=>"200", "Field A"=>"789", "Field B"=>"999", "Field C" => "", "Field D" => "444"}
]

我可以如下获取唯一 ID 和密钥

irb(main):099:0> unique_id = input.map { |p| p["ID"] }.uniq
=> ["100", "200"]
irb(main):100:0> unique_keys = input.map { |p| p["Key"] }.uniq
=> ["Field A", "Field B", "Field C", "Field D"]

但是,我无法继续为每个包含输入哈希中定义的键/值对的 ID 创建唯一的哈希数组。

【问题讨论】:

  • 我在答案中添加了所有键,检查已编辑的答案。
  • 所需的结构没有应有的用处。一个散列数组,其中“ID”在每个散列中是唯一的,可以更好地存储为单个散列。这个问题实际上看起来像一个 XY 问题,你在问如何做“Y”,但实际上应该问“X”。 “What is the XY problem?”。

标签: ruby hashtable


【解决方案1】:

尝试关注,

fields = input.map {|x| x['Key'] }.uniq

output = input.group_by  { |x| x['ID'] }
     .map { |k,v| ([['ID', k]] + v.map {|z| z.values_at('Key','Value') }).to_h }

output.map! { |x| {'ID' => x['ID']}.merge fields.to_h {|z| [z, x[z].to_s]} }

输出将是,

[
  {"ID"=>"100", "Field A"=>"123", "Field B"=>"333", "Field C"=>"555", "Field D"=>""}, 
  {"ID"=>"200", "Field A"=>"789", "Field B"=>"999", "Field C"=>"", "Field D"=>"444"}
]

【讨论】:

  • @SebastianPalma 是的,谢谢,在编辑中添加,但由于我使用的方法,键的顺序不符合规定!
  • v.map {|z| [z['Key'], z['Value']]} 可以替换为v.map {|z| z.values_at('Key', 'Value') },我个人觉得它更具可读性。并且fields.map {|z| [z, x[z].to_s]}.to_h 可以替换为fields.to_h {|z| [z, x[z].to_s]}
  • @3limin4t0r 谢谢,我在追求输出时错过了它:P 编辑答案!
  • 不错的一个。 x[z].to_s 很可爱,但只有当值是字符串时才有效。在示例中确实如此,但没有理由限制一般性。更好:x[z] || ""。最佳(imo,oc):x.key?(z) ? x[z] : "",我认为它读起来更好,并且还允许在input 中使用预期的nil 值。
【解决方案2】:

这样的事情可能会完成这项工作:

keys = input.map { |hash| hash['Key'] }.uniq
result = Hash.new { |result, id| result[id] = {} }
input.each { |hash| result[hash['ID']].merge!(hash['Key'] => hash['Value']) }
result.default = nil # optional: remove the default value

result.each do |id, hash| 
  (keys - hash.keys).each { |key| hash[key] = '' }
  hash['ID'] = id
end    

result.values
#=> [{"Field A"=>"123", "Field B"=>"333", "Field C"=>"555", "Field D"=>"", "ID"=>"100"},
#    {"Field A"=>"789", "Field B"=>"999", "Field D"=>"444", "Field C"=>"", "ID"=>"200"}]

如果您确定某些值永远不会虚假,则可以替换:

(keys - hash.keys).each { |key| hash[key] = '' }
# with
keys.each { |key| hash[key] ||= '' }

我首先创建了一个哈希result 来保存生成的哈希,我将值设置为默认值以一个新的哈希。然后我根据 ID 得到正确的散列,并将键值对合并到散列中。最后,我将缺少的键添加到散列中,并将它们的值设置为空字符串,并将散列保存到散列下的 ID 添加到散列中。

注意:如果您的 input 数组包含重复的键值对,则将使用最后一个。例如,假设 {"ID"=>"100", "Key"=>"Field A", "Value"=>"123"}{"ID"=>"100", "Key"=>"Field A", "Value"=>"456"} 都存在。然后将设置"Field A" => "456",因为它是两者中的后者。

【讨论】:

  • result[hash['ID']].merge!(hash['Key'] => hash['Value']) 可以替换为result[hash['ID']][hash['Key']] = hash['Value'],但是我发现第一个更好读。
  • 谢谢,它几乎对我有用。但是,我也希望看到所有哈希中的键也相同。空白“字段 D”=>“”表示 ID = 100 空白“字段 C”=>“”表示 ID = 200
  • 太棒了!!它有效.. 这是我在 StackOverflow 上的第一个问题。很高兴知道周围有如此出色的技术社区。​​span>
  • @SKools 我的解决方案不会按照您想要的确切顺序返回键值对,但是大多数时候您并不关心哈希的顺序。如果您出于某种原因这样做,answer of ray 也会返回正确的顺序。然而,在我看来,他的回答使用了太多的首字母缩略词,尽管我在很大程度上同意他选择的解决方案。
  • 不错的解决方案。无需更新:(永远)。就像您正在编辑您撰写的文章的草稿一样进行编辑。那样流动得更好。如果您避免读者需要水平滚动,也会更容易阅读。
【解决方案3】:

我的答案分为三个步骤。

第一步:获取"ID"的唯一值和"Field X"形式的唯一键

ids, keys = input.map { |h| h.values_at("ID", "Key") }.transpose.map(&:uniq)
  #=> [["100", "200"], ["Field A", "Field B", "Field C", "Field D"]] 

Hash#values_at。计算如下:

a = input.map { |h| h.values_at("ID", "Key") }
  #=> [["100", "Field A"], ["100", "Field B"], ["100", "Field C"],
  #    ["200", "Field A"], ["200", "Field B"], ["200", "Field D"]] 
b = a.transpose
  #=> [["100", "100", "100", "200", "200", "200"],
  #    ["Field A", "Field B", "Field C", "Field A", "Field B", "Field D"]] 
ids, keys = b.map(&:uniq)
  #=> [["100", "200"], ["Field A", "Field B", "Field C", "Field D"]] 
ids
  #=> ["100", "200"] 
keys
  #=> ["Field A", "Field B", "Field C", "Field D"] 

第 2 步:构造一个哈希,其键是 "ID" 的唯一值,其值是要在第 3 步中完成和提取的哈希

h = ids.each_with_object({}) { |id,h|
  h[id] = keys.each_with_object("ID"=>id) { |key,g| g[key] = "" } }
  #=> {"100"=>{"ID"=>"100", "Field A"=>"", "Field B"=>"", "Field C"=>"",
  #            "Field D"=>""},
  #    "200"=>{"ID"=>"200", "Field A"=>"", "Field B"=>"", "Field C"=>"",
  #            "Field D"=>""}}

第 3 步:遍历 input 以完成第 2 步中构造的哈希值,然后,作为最后一步,从该哈希中提取值

input.each_with_object(h) { |g,h| h[g["ID"]].update(g["Key"]=>g["Value"]) }.values
  #=> [{"ID"=>"100", "Field A"=>"123", "Field B"=>"333", "Field C"=>"555",
  #     "Field D"=>""},
  #    {"ID"=>"200", "Field A"=>"789", "Field B"=>"999", "Field C"=>"",
  #     "Field D"=>"444"}]

Hash#update(又名merge!)和Hash#values。两种计算方式如下:

h = input.each_with_object(h) { |g,h| h[g["ID"]].update(g["Key"]=>g["Value"]) }
  #=> {"100"=>{"ID"=>"100", "Field A"=>"123", "Field B"=>"333","Field C"=>"555",
  #            "Field D"=>""},
  #    "200"=>{"ID"=>"200", "Field A"=>"789", "Field B"=>"999","Field C"=>"",
  #            "Field D"=>"444"}} 
h.values
  #=> <as above>     

【讨论】:

    【解决方案4】:

    输出结构不是我想要使用的,看起来输入结构正在影响所需的输出。这导致XY problem

    哈希非常有效,尤其是当您在数据库中有类似索引字段的东西时。与哈希相比,遍历数组以查找值的效率极低,因此我建议再看一下这两个结构。

    将输入转换为真正的哈希并不难:

    input = [
      {"ID"=>"100", "Key"=>"Field A", "Value"=>"123"},  
      {"ID"=>"100", "Key"=>"Field B", "Value"=>"333"},  
      {"ID"=>"100", "Key"=>"Field C", "Value"=>"555"},  
      {"ID"=>"200", "Key"=>"Field A", "Value"=>"789"},  
      {"ID"=>"200", "Key"=>"Field B", "Value"=>"999"},  
      {"ID"=>"200", "Key"=>"Field D", "Value"=>"444"}   
    ]                                                   
    
    output = Hash.new { |h, k| h[k] = {} }  # => {}
    input.each { |e|      
      id = e['ID']        
      key = e['Key']      
      value = e['Value']  
    
      output[id][key] = value 
    }                         
    

    结果:

    output
    # => {"100"=>{"Field A"=>"123", "Field B"=>"333", "Field C"=>"555"},
    #     "200"=>{"Field A"=>"789", "Field B"=>"999", "Field D"=>"444"}}
    

    这样做的好处非常明显,如果你想要"200" 的数据,很容易获取:

    output['200'] # => {"Field A"=>"789", "Field B"=>"999", "Field D"=>"444"}
    output['200']['Field B'] # => "999"
    

    【讨论】:

      【解决方案5】:
      keys = input.map { |hash| hash['Key'] }.uniq
      
      output = input.group_by  { |x| x['ID'] }.map { |k,v| ([['ID', k]] + v.map {|z| z.values_at('Key','Value') }).to_h }
      
      output.map! { |x| {'ID' => x['ID']}.merge fields.map {|z| [z, x[z].to_s]}.to_h }
      

      下面给了我如下所示的输出

      [
       {"ID"=>"100", "Field A"=>"123", "Field B"=>"333", "Field C"=>"555", "Field D"=>""}, 
       {"ID"=>"200", "Field A"=>"789", "Field B"=>"999", "Field C"=>"", "Field D"=>"444"}
      ]
      

      感谢大家的意见

      【讨论】:

      • 这只是answer of ray 的简单转发。您只需将fields.to_h { ... } 更改为fields.map { ... }.to_h。如果某个答案由于您使用的是较旧的 Ruby 版本而不起作用,您可以在他的答案下发表评论,以便他可以相应地更新他的答案。我建议删除这个答案并简单地给他复选标记,评论你使用低于 2.6.0 的 Ruby 版本并且 to_h 不可用。
      • 我在每一点上都同意@3limin4t0r。另请注意,要使此代码正常工作,必须将第一行中的keys = 替换为fields =。如果你留下你的答案,它很快就会开始吸引反对票......
      猜你喜欢
      • 2010-12-11
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多