繁体   English   中英

具有重复值的哈希数组的哈希数组

[英]ruby array of array with repeated values to hash of hash

我是ruby的新手,我很难搞清楚如何将数组数组转换为数组哈希的哈希值。

例如,说我有:

[ [38, "s", "hum"], 
  [38, "t", "foo"], 
  [38, "t", "bar"], 
  [45, "s", "hum"], 
  [45, "t", "ram"], 
  [52, "s", "hum"], 
  [52, "t", "cat"], 
  [52, "t", "dog"]
]

我最终想要的是:

{38 => {"s" => ["hum"],
        "t" => ["foo", "bar"]
       },
 45 => {"s" => ["hum"],
        "t" => ["ram"]
       },
 52 => {"s" => ["hum"],
        "t" => ["cat", "dog"]
       }
 }

我尝试过group_by和Hash,但两个都没有给我我正在寻找的东西。

也许有一种更简洁的方法可以做到这一点,但我决定采用直截了当的方式:

input = [ [38, "s", "hum"],
  [38, "t", "foo"],
  [38, "t", "bar"],
  [45, "s", "hum"],
  [45, "t", "ram"],
  [52, "s", "hum"],
  [52, "t", "cat"],
  [52, "t", "dog"]
]

output = {}

# I'll talk through the first iteration in the comments.

input.each do |outer_key, inner_key, value|
  # Set output[38] to a new hash, since output[38] isn't set yet.
  # If it were already set, this line would do nothing, so
  # output[38] would keep its previous data.
  output[outer_key] ||= {}

  # Set output[38]["s"] to a new array, since output[38]["s"] isn't set yet.
  # If it were already set, this line would do nothing, so
  # output[38]["s"] would keep its previous data.
  output[outer_key][inner_key] ||= []

  # Add "hum" to the array at output[38]["s"].
  output[outer_key][inner_key] << value
end

所以,你实际使用的部分,全部整理:

output = {}

input.each do |outer_key, inner_key, value|
  output[outer_key] ||= {}
  output[outer_key][inner_key] ||= []
  output[outer_key][inner_key] << value
end

在这种情况下, inject (在1.9中reduce )是一个很好的工具:

input.inject({}) do |acc, (a, b, c)|
  acc[a] ||= {}
  acc[a][b] ||= []
  acc[a][b] << c
  acc
end

它将通过累加器和项目为input每个项目调用块一次。 它第一次将参数作为累加器传递,后续调用将最后一次调用的返回值作为累加器。

根据您的敏感度,这可能被视为可怕或优雅:

input.inject(Hash.new {|h1,k1| h1[k1] = Hash.new {|h2,k2| h2[k2] = Array.new}}) {|hash,elem| hash[elem[0]][elem[1]].push(elem[2]); hash}
=> {38=>{"s"=>["hum"], "t"=>["foo", "bar"]}, 45=>{"s"=>["hum"], "t"=>["ram"]}, 52=>{"s"=>["hum"], "t"=>["cat", "dog"]}}

理想情况下,更易读的版本是:

input.inject(Hash.new(Hash.new(Array.new))) {|hash,elem| hash[elem[0]][elem[1]].push(elem[2]); hash}

也就是说,从空哈希开始,默认值等于空哈希值,默认值等于空数组。 然后迭代输入,将元素存储在适当的位置。

后一种语法的问题是Hash.new(Hash.new(Array.new))将导致所有散列和数组在内存中具有相同的位置,因此值将被覆盖。 前一种语法每次都会创建一个新对象,从而得到所需的结果。

问题中给出的示例对于每个元素数组的长度为3,但下面的方法使用递归,并且可以用于任意长度。

a = [ [38, "s", "hum", 1], 
    [38, "t", "foo", 2],
    [38, "t", "bar", 3], 
    [45, "s", "hum", 1], 
    [45, "t", "ram", 1], 
    [52, "s", "hum", 3], 
    [52, "t", "cat", 3], 
    [52, "t", "dog", 2]
]

class Array
  def rep
    group_by{|k, _| k}.
    each_value{|v| v.map!{|_, *args| args}}.
    tap{|h| h.each{|k, v| h[k] = (v.first.length > 1 ? v.rep : v.flatten(1))}}
  end
end

p a.rep

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM