【问题标题】:Selective merge of array of hashes in RubyRuby中哈希数组的选择性合并
【发布时间】:2015-08-15 09:13:17
【问题描述】:

我有两个包含哈希的数组:

a = [
  {:umc=>"11VE", :title=>"FOOBARS"},
  {:umc=>"1973", :title=>"ZOOBARS"},
  {:umc=>"1140", :title=>"TINYBAR"},
]

b = [
  {:umc=>"11VE", :code=>"23"},
  {:umc=>"10EE", :code=>"99"},
  {:umc=>"1140", :code=>"44"},
  {:umc=>"1973", :code=>"55"},
]

并希望有选择地将它们合并到另一个具有哈希值的数组中,如下所示:

c = [
  {:umc=>"11VE", :title=>"FOOBARS", :code=>"23"},
  {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"},
  {:umc=>"1140", :title=>"TINYBAR"} :code=>"44"},
]

我正在使用代码

combo=(a+b).group_by{|h| h[:umc]}.map{|k,v| v.reduce(:merge)}

合并两个数组就好了,但我希望结果只包含出现在第一个数组中的项目。

作为第二个想法,能够有两个结果会很棒,一个由两个初始数组组合的项目组成,第二个包含第一个但不在第二个中的元素。

【问题讨论】:

  • :umc=>"10EE" 怎么样?
  • 抱歉 10EE 不应该出现在第一个数组中。不用说,实际数据有超过 10,000 项。谢谢你的收获。

标签: arrays ruby hash


【解决方案1】:
b.reduce(a) do |memo, e| 
  (ae = memo.detect { |ae| ae[:umc] == e[:umc] }) && ae.merge!(e)
  memo
end

Cary 提出了“更可读”的方式来表达合并,再加上我们是否不想改变初始的a 数组,我们应该dup 它的元素:

b.reduce(a.map(&:dup)) do |memo, e| 
  ae = memo.detect { |ae| ae[:umc] == e[:umc] }
  ae.merge!(e) if ae
  memo
end

【讨论】:

  • 很好的解决方案,但需要注意的是它会改变a的元素。 Imo,如果将第一行分成两部分会更好读,后者是ae.merge!(e) if ae
  • 如果a 中的两个哈希值与:umc 相同,您也可以将b.reduce(a) 更改为(a+b).reduce({})。 (这是我犯的一个错误,已被 OP 标记出来。)
  • @CarySwoveland 我添加了.dup 以避免初始a 数组的突变,谢谢。关于你的第二条评论,我没有得到一点。结果无论如何都不会削减a,它只会将它与b合并。
  • @CarySwoveland - 第一个提议的代码实际上可以完成我想要的一切,或者至少我认为它可以。我的计划是查看所有其他解决方案,直到我理解为什么其中一些解决方案只包含 :umc 在两个数组中都匹配的结果。该解决方案按应有的方式进行合并,并根据匹配保留一段时间添加来自 b 的元素。感谢您的解决方案和教育。
  • @berlin 欢迎您。请注意减少a(因为在Cary 指出由于就地merge! 而我们改变初始a 数组的方式之前)和a.dup 之间的区别,它不会改变任何初始输入。后者似乎更可靠。
【解决方案2】:

不是很优雅,但在计算方面应该非常有效,因为它平均为 O(n+m)。我怀疑你能比这更快地得到它。不利的一面是,它会占用大量空间。

def selective_merge(a, b)
  merged = {}
  a.each {|v| merged[v[:umc]] = [v[:umc], v[:title]]}
  b.each {|v| merged[v[:umc]] = merged[v[:umc]] << v[:code] if merged[v[:umc]]}
  merged.map {|k,v| {:umc => v[0], :title => v[1], :code => v[2]}}
end

【讨论】:

  • 您还可以通过传入选择操作使其更通用,并使其与哈希中的更多键一起使用。
  • 这是一个很好的答案,但我还是 Ruby 和 SO 的新手。两个初始数组可能有任意数量的元素,所以我想调整代码以合并两个哈希的所有元素,如果元素 :umc 匹配,而不必指定它们。
【解决方案3】:

试试这个

# @a - Pivot array(elements that will be in result array)
# @b - Array which contains intersecting elements
# @group - attribute name, which identify similarity of elements
def c_join(a, b, group)
  result = []
  a.each do |a_i|
    similar = b.detect{ |b_i| b_i[group] == a_i[group] }
    result << a_i.merge(similar)
  end
  result
end

测试

a = [
  {:umc=>"11VE", :title=>"FOOBARS"},
  {:umc=>"1973", :title=>"ZOOBARS"},
  {:umc=>"1140", :title=>"TINYBAR"},
]

b = [
  {:umc=>"11VE", :code=>"23"},
  {:umc=>"10EE", :code=>"99"},
  {:umc=>"1140", :code=>"44"},
  {:umc=>"1973", :code=>"55"},
]

c_join(a, b, :umc)

结果

[
  {:umc=>"11VE", :title=>"FOOBARS", :code=>"23"}, 
  {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"}, 
  {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}
]

【讨论】:

    【解决方案4】:

    您可以如下修改您的代码:

    bb = b.select { |f| a.any? { |h| h[:umc] == f[:umc] } }
      #=> [{:umc=>"11VE", :code=>"23"},
      #    {:umc=>"1140", :code=>"44"},
      #    {:umc=>"1973", :code=>"55"}] 
    (a + bb).group_by { |g| g[:umc] }.map { |_,v| v.reduce(:merge) }
      #=> [{:umc=>"11VE", :title=>"FOOBARS", :code=>"23"},
      #    {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"},
      #    {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}]
    

    但这样计算bb 会更有效:

    require 'set'
    umc_a_vals = a.map { |g| g[:umc] }.to_set
      #=> #<Set: {"11VE", "1973", "1140"}>
    bb = b.select { |f| umc_a_vals.include(f[:umc]) }
      #=> [{:umc=>"11VE", :code=>"23"},
      #    {:umc=>"1140", :code=>"44"},
      #    {:umc=>"1973", :code=>"55"}] 
    

    这是另一种方式:

    f = a.group_by { |g| g }.map { |_,v| v.reduce(:merge) }
      #=> [{:umc=>"11VE", :title=>"FOOBARS"},
      #    {:umc=>"1973", :title=>"ZOOBARS"},
      #    {:umc=>"1140", :title=>"TINYBAR"}] 
    b.each_with_object(f) do |g,h| 
      (h.update(g[:umc]=>g) { |_,o,n| o.merge(n) }) if h.key?(g[:umc])
    end.values
      #=> [{:umc=>"11VE", :title=>"FOOBARS", :code=>"23"},
      #    {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"},
      #    {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}]
    

    f 的计算与你的代码非常相似,我认为不需要解释。

    我们现在合并哈希:

    { g[:umc]=g }
      #=> { "11VE"=>{:umc=>"11VE", :code=>"23"} }
    

    如果h 有一个密钥"11VE",则进入h,它确实如此。为此,我们使用Hash#update(又名merge!)的形式,它使用了块:

    { |_,o,n| o.merge(n) }
    

    确定在被合并的两个哈希中存在的键的值。

    块变量相等:

    _ #=> "11VE"
    o #=> {:umc=>"11VE", :title=>"FOOBARS"}
    n #=> {:umc=>"11VE", :code=>"23"}
    

    所以分块计算的结果是:

    o.merge(n)
      #=> {:umc=>"11VE", :title=>"FOOBAR, :code=>"23"}
    

    这是h[:umc]的更新值。

    顺便说一句:我使用局部变量_ 作为键的值,以提醒人们注意它没有用于块计算。变量on 通常分别用于表示“旧”和“新”值。

    b 的剩余值构造的散列合并到h 的过程类似。

    最后一步是提取h的值。

    作为第二个例子,假设:

    a = [
      {:umc=>"11VE", :title=>"FOOBARS"},
      {:umc=>"11VE", :title=>"ZOOBARS", :author=>"Billy-Bob"},
      {:umc=>"1140", :title=>"TINYBAR"}
    ]
    

    我们得到:

    f = a.each_with_object({}) { |g,h| h.update(g[:umc]=>g) { |_,o,n| o.merge(n) } }  
      #=> {"11VE"=>{:umc=>"11VE", :title=>"ZOOBARS", :author=>"Billy-Bob"},
      #    "1140"=>{:umc=>"1140", :title=>"TINYBAR"}} 
    b.each_with_object(f) do |g,h| 
      (h.update(g[:umc]=>g) { |_,o,n| o.merge(n) }) if h.key?(g[:umc])
    end.values
      #=> [{:umc=>"11VE", :title=>"ZOOBARS", :author=>"Billy-Bob", :code=>"23"},
      #    {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}] 
    

    【讨论】:

    • 您的第二个示例运行良好,但它似乎丢弃了 a 中与 b 中没有对应匹配项的任何元素。无论与 b 匹配如何,我都需要保留 a 的所有元素。我只是不想要任何没有出现在 a 中的 b。我正在慢慢研究每个建议的解决方案,主要是为了让自己加快使用 ruby​​(2.0 版)
    • 好收获。 (读者:我提供的第二种方法的问题是如果ghag[:umc]=h[:umc] 的元素,第一个哈希将被第二个覆盖)。我已经修复了它并给出了另一个例子。
    猜你喜欢
    • 2011-11-19
    • 2017-12-06
    • 2015-05-07
    • 2019-11-21
    • 2017-01-16
    • 1970-01-01
    • 1970-01-01
    • 2014-10-28
    • 2015-06-13
    相关资源
    最近更新 更多