【问题标题】:Iterate through array of hashes and merge [closed]遍历哈希数组并合并[关闭]
【发布时间】:2015-04-06 22:39:49
【问题描述】:

我有一个这样的哈希数组:

[{:a=>"a", :b=>"b", :c=>"c", :d=>"d"}, 
 {:a=>"a", :b=>nil, :c=>"notc", :d=>"d"}]

我想遍历哈希并将它们合并到特定键相同的位置,例如:a。如果下一个键元素相同 - 忽略它,但如果键不同,则创建一个数组。结果将如下所示:

{:a=>"a", :b=>"b", :c=>["c","notc"], :d=>"d"}

我想我已经对哈希数组做了一个 for 循环,然后使用 merge! 方法,但不知道从哪里开始

【问题讨论】:

  • 为什么你想要:a => "a",但是:d => ["d"]?您如何区分这些情况?
  • “下一个关键元素”是什么意思?除了插入顺序外,散列不需要也很少有顺序中的键。如果您迭代哈希,您通常会得到与迭代哈希键的排序列表不同的顺序。另外,你试过什么?预计您会向我们展示您的尝试,以便我们帮助您修复它,而不是我们为您编写一些东西,然后您将其硬塞到位。
  • @lurker 打错字了,我现在改正了
  • 为什么:b的值不是["b", nil]?
  • 进一步提到@sawa 的观点,为了使期望的结果与示例一致,请:1)将第二个哈希中的:b=>nil 更改为:b=>"b" 或2)在您的输出中更改:b=>"b"哈希到["b",nil]。我想你也可以解释你不希望nil(或重复值)合并,但是如果没有其他哈希有键:b,你还必须解释是否要保留:b=>nil。跨度>

标签: ruby arrays hashmap


【解决方案1】:

我也会像这样使用Hash#merge!(又名update)(让a 表示您的哈希数组的名称):

a.each_with_object({}) do |g,h|
  h.update(g) do |_,o,n|
    case o
    when Array
      o.include?(n) ? o : o + [n]
    else
      o.eql?(n) ? o : [o,n] 
    end
  end
end
  #=> {:a=>"a", :b=>["b", nil], :c=>["c", "notc"], :d=>"d"} 

当o是一个数组时,如果不想合并nil的值,将以下行改为:

(o.include?(n) || n.nil?) ? o : o + [n]

步骤:

a = [{:a=>"a",  :b=>"b", :c=>"c",    :d=>"d"}, 
     {:a=>"a",  :b=>nil, :c=>"notc", :d=>"d"},
     {:a=>"aa", :b=>"b", :c=>"cc",   :d=>"d"},
]

enum = a.each_with_object({})
  #-> #<Enumerator: [{:a=>"a",  :b=>"b", :c=>"c",    :d=>"d"},
  #                  {:a=>"a",  :b=>nil, :c=>"notc", :d=>"d"},
  #                  {:a=>"aa", :b=>"b", :c=>"cc",   :d=>"d"}]
  #    :each_with_object({})> 

我们可以通过将枚举器转换为数组来查看枚举器的值(将被传递到块中):

enum.to_a
  #=> [[{:a=>"a",  :b=>"b", :c=>"c",    :d=>"d"}, {}],
  #    [{:a=>"a",  :b=>nil, :c=>"notc", :d=>"d"}, {}],
  #    [{:a=>"aa", :b=>"b", :c=>"cc",   :d=>"d"}, {}]] 

传入第一个值并将其分配给块变量:

g,h = enum.next
  #=> [{:a=>"a", :b=>"b", :c=>"c", :d=>"d"}, {}] 
g #=> {:a=>"a", :b=>"b", :c=>"c", :d=>"d"} 
h #=> {} 

update 的块用于确定在被合并的两个散列中存在的键的值。由于h 目前为空({}),因此它不用于此合并:

h.update(g)
  #=> {:a=>"a", :b=>"b", :c=>"c", :d=>"d"} 

h 的新值被返回。

现在将enum 的第二个值传递到块中:

g,h = enum.next
  #=> [{:a=>"a", :b=>nil, :c=>"notc", :d=>"d"},
  #    {:a=>"a", :b=>"b", :c=>"c",    :d=>"d"}] 
g #=>  {:a=>"a", :b=>nil, :c=>"notc", :d=>"d"} 
h #=>  {:a=>"a", :b=>"b", :c=>"c",    :d=>"d"} 

并执行:

h.update(g)

当要合并来自g 的:a=&gt;"a" 时,update 会看到h 包含相同的键:a。因此,它根据块来确定合并散列中:a 的值。它将以下值传递给块:

k = :a
o = "a"
n = "a"

其中k 是键,o(用于“旧”)是h 中k 的值,n(用于“新”)是@ 中k 的值987654352@。 (我们没有在块中使用k,所以我将块变量命名为_ 来表示。)在case 语句中,o 不是一个数组,所以:

o.eql?(n) ? o : [o,n]
  #=> "a".eql?("a") ? "a" : ["a","a"] 
  #=> "a"

返回到 update 以作为 :a 的值。即值不变。

当密钥为:b时:

k = :b
o = "b"
n = nil

同样,o 不是数组,所以我们再次执行:

o.eql?(n) ? o : [o,n] 
  #=> ["b", nil] 

但这次返回的是一个数组。合并enum 的第二个元素的其余计算类似地进行。合并后:

h #=> {:a=>"a", :b=>["b", nil], :c=>["c", "notc"], :d=>"d"} 

当enum的第三个元素中的:c=&gt;"cc"被合并时,以下值被传递给update的块:

_ :c
o = ["c", "notc"]
n = "cc"

由于o是一个数组,我们执行case语句的下面一行:

o.include?(n) ? o : o + [n]
  #=> ["c", "notc", "cc"]

并且:c 的值被分配了该值。其余的计算类似地执行。

【讨论】:

  • 很遗憾,您的结果不符合 :b 键的要求。 OP想要:b=&gt;"b"。
  • @spickermann,我注意到在阅读@sawa 的评论后,并在上面显示了所需的调整(“当o 是一个数组时,......”)。我还对这个问题发表了评论,建议 OP 澄清一下。
  • 你说得对,关于如何处理nil 值的要求并不清楚。我希望OP能澄清这个问题。
  • 如果我可以为广度和清晰度加分,我会的。哇!
【解决方案2】:

我会这样做:

array = [{ :a=>"a", :b=>"b", :c=>"c",    :d=>"d" }, 
         { :a=>"a", :b=>nil, :c=>"notc", :d=>"d" }]

result = array.reduce({}) do |memo, hash|
  memo.merge(hash) do |_, left, right|
    combined = Array(left).push(right).uniq.compact
    combined.size > 1 ? combined : combined.first
  end
end

puts result
#=> { :a=>"a", :b=>"b", :c=>["c", "notc"], :d=>"d" }

Array(left) 将确保一个哈希值是一个数组。 push(right) 将来自另一个哈希的值添加到该数组中。 uniq.compact 删除 nil 值和重复项。

如果数组只包含一个元素,combined.size &gt; 1 ? combined : combined.first 行只返回元素。

【讨论】:

    【解决方案3】:

    假设hs = [{:a=&gt;"a", :b=&gt;"b", :c=&gt;"c", :d=&gt;"d"}, {:a=&gt;"a", :b=&gt;nil, :c=&gt;"notc", :d=&gt;"d"}],您可以使用(诚然,非常密集的)单线做到这一点:

    Hash[hs.map { |h| h.map { |k,v| [k, v] } }.sort_by(&:first).reduce { |left,right| left.zip(right) }.map { |a| [a.first.first, a.map(&:last).compact.uniq] }]
    

    解开这里发生的事情:

    首先我们使用map 将哈希数组转换为对数组的数组,然后使用sort_by 对数组进行排序,使所有键都“对齐”。

    [{:a=&gt;"a", :b=&gt;"b", :c=&gt;"c", :d=&gt;"d"}, {:a=&gt;"a", :b=&gt;nil, :c=&gt;"notc", :d=&gt;"d"}]变成

    [[[:a, "a"], [:b, "b"], [:c, "c"], [:d, "d"]], [[:a, "a"], [:b, nil], [:c, "notc"], [:d, "d"]]]

    就是这个部分:

     hs.map { |h| h.map { |k,v| [k, v] } }.sort_by(&:first).
    

    然后,我们使用reduce 并将所有数组压缩在一起,zip 只需要两个数组[1,2,3]、[:a,:b,:c] 并输出一个数组,如:[[1,:a],[2,:b],[3,:c]]

        reduce { |left,right| left.zip(right) }.
    

    此时我们已经将所有数据按key分组在一起,需要对key的所有副本进行去重,我们可以去掉nils并将值uniq:

        map { |a| [a.first.first, a.map(&:last).compact.uniq] }]
    

    这是一个撬动会话的示例:

    [31] pry(main)> Hash[hs.map { |h| h.map { |k,v| [k, v] } }.sort_by(&:first).reduce { |left,right| left.zip(right) }.map { |a| [a.first.first, a.map(&:last).compact.uniq] }]
    => {:a=>["a"], :b=>["b"], :c=>["c", "notc"], :d=>["d"]}
    

    【讨论】:

    • 我几乎觉得这应该在 CodeGolf.SE 中
    • 与其提交令人费解的东西,为什么不把它分解成更容易理解的东西,以使 OP 受益。解释它为什么起作用。毕竟,您应该帮助教育,而不是混淆解决方案。而且,如果单行很重要,请解释原因,否则将其分成多行以提高可读性。
    • 你可能有一个很好的解决方案,但你不鼓励读者去看看。要做的第一件事,只需要几秒钟,就是把它分成几行,这样读者就不必水平滚动来阅读它。 (别担心,它仍然有资格作为单线。:-))。例如,您可以将.sort_by... 放在hs.map... 下,点对齐,然后在其下方,.reduce...,在其下方map....,等等。
    • 很恶心,但也展示了很多有趣的功能。我同意这对新手不友好,但有时他们可以看到非新手友好的答案。 (我不是在批准拖钓,只是说有时脱掉孩子手套是可以的)。
    • 另外,我看到过由技术精湛、受人尊敬和喜爱的开发人员编写的更糟糕的作品。
    猜你喜欢
    • 1970-01-01
    • 2016-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    相关资源
    最近更新 更多