将多维数组转换为哈希表而不覆盖值

3

我有一个多维数组,例如:

array = [["stop", "halt"],["stop", "red"],["go", "green"],["go","fast"],["caution","yellow"]]

我希望你能将它转化为类似于这样的哈希值:
hash = {"stop" => ["halt","red"], "go" => ["green","fast"], "caution" => "yellow"}

然而,当我使用array.to_h时,值会互相覆盖,出现以下情况:
hash = {"stop" => "red", "go" => "fast", "caution" => "yellow"}

我该如何获得所需的数组?

2个回答

2
这是一种方法。它使用Enumerable#each_with_objectHash#update的形式(也称为merge!),该形式使用块来确定合并的两个哈希中都存在的键的值。
array << ["stop", "or I'll fire!"]

array.each_with_object({}) { |(f,l),h|
  h.update(f=>l) { |_,ov,nv| ov.is_a?(Array) ? ov << nv : [ov, nv] } }
  #=> {"stop"=>["halt", "red", "or I'll fire!"],
  #    "go"=>["green", "fast"],
  #    "caution"=>"yellow"}

如果你希望哈希表中的所有值都变成数组(例如"caution"=>["yellow"]),从而简化代码,这通常更方便后续计算。
array.each_with_object({}) { |(f,l),h|  h.update(f=>[l]) {|_,ov,nv| ov+nv }}
  #=> {"stop"=>["halt", "red", "or I'll fire!"],
  #    "go"=>["green", "fast"],
  #    "caution"=>["yellow"]}

谢谢你,Cary!我只学了几周编程(任何语言),所以我还在努力熟悉许多方法和缩写。我一直在搜索好的解释each_with_object的文章,但找不到真正让我理解它的东西。你知道我可以在哪里找到一个好的解释吗?此外,有没有一种有效的方法可以将所有值保留为数组? - Garett Arrowood
Garrett,Enumerable#each_with_object并没有什么神秘的地方,它在1.9版本中首次亮相。假设a是一个数组,你希望做到这一点:b = []; a.each { |e| b << e+1; b},也就是说,1.创建一个对象(b=[]);2.对a的每个元素进行操作;3.返回该对象(b)。这是一个非常常见的模式。a.each_with_object([]) { |e,b| b << e+1 }可以让你用一行代码完成这三个步骤。没有更多,也没有更少。把这个方法看作是“each,带有一个对象”。我会编辑以回答你的另一个问题。 - Cary Swoveland
谢谢你的解释和上面额外提供的代码!这为问题添加了很多澄清。 - Garett Arrowood

1
一种方法是:

这样做:

array.inject({}) {|r, (k, v)| r[k] &&= [*r[k], v]; r[k] ||= v; r }

但这样写很混乱。详细写出来,看起来像这样:

def to_hash_with_duplicates(arr)
  {}.tap do |r|
    arr.each do |k, v|
      r[k] &&= [*r[k], v]    # key already present, turn into array and add value
      r[k] ||= v             # key not present, simply store value
    end
  end
end

编辑:再仔细思考,@cary-swoveland的块更新解决方案更好,因为它可以正确处理nilfalse值。

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