如何从两个哈希数组中获取并集/交集/差集并忽略某些键?

7

我希望从两个散列数组中获取并集、交集和差集,例如:

array1 = [{:name =>'Guy1', :age => 45},{:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1', :age => 45},{:name =>'Guy3', :age => 45}]

...

p array1 - array2 

=> [{:name=>"Guy2", :age=>45}]


p array2 - array1
=> [{:name=>"Guy3", :age=>45}]


p array1 | array2 
=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy3", :age=>45}]

然而,当我只想基于名称进行比较并忽略年龄,而无需将它们从哈希表中移除时,例如:
array1 = [{:name =>'Guy1', :age => 45},{:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1', :age => 46},{:name =>'Guy3', :age => 45}]

在这种情况下,我没有得到想要的结果,因为年龄不同。
array1 - array2 

=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}]

array2 - array1
=> [{:name=>"Guy1", :age=>46}, {:name=>"Guy3", :age=>45}]

array1 | array2 
=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy1", :age=>46}, {:name=>"Guy3", :age=>45}]

有没有一种方法可以获取并忽略 age 键的 union/intersect/difference?
编辑:为了更好地说明:
array1 = [{:name =>'Guy1', :age => 45},{:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1'},{:name =>'Guy3'}]

p array1 - array2
p array2 - array1
p array1 | array2
p array1 & array2

感谢你提前提供的帮助!

预期结果是什么,特别是 :age 的值是多少? - sawa
预期结果应该就像:age不存在一样。 - Mark Thomas
预期结果应与第一个示例相同。 - Seth Pollack
3个回答

7

以下是一个快速且简单的获取并集的方法:

(array1 + array2).uniq{|a| a[:name]}

然而,我建议创建你自己的Hash子类,这样你就可以安全地覆盖eql?方法。正如Cary Swoveland指出的那样,集合运算符依赖于它。请注意,你还需要限制hash方法仅在名称字段上提供哈希函数。
class Guy < Hash

  def eql?(other_hash)
    self[:name] == other_hash[:name]
  end

  def hash
    self[:name].hash
  end

end

这些 Guy 对象将在所有集合操作中起作用:

array1 = [ Guy[name:'Guy1', age: 45], Guy[name:'Guy2', age: 45] ]
array2 = [ Guy[name:'Guy1', age: 46], Guy[name:'Guy3', age: 45] ]

array1 - array2
#=> [{:name=>"Guy2", :age=>45}]

array2 - array1
#=> [{:name=>"Guy3", :age=>45}]

array1 | array2
#=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy3", :age=>
45}]

array1 & array2
#=> [{:name=>"Guy1", :age=>45}]

即使是子类化也可能存在危险,但是相比于操纵Hash类来说,这样做更好。如果fgGuy对象,当本意是想要使用f == g时,可能会无意中使用f.eql?(g)(更改eql?不会影响=),这可能导致相当严重的错误。 - Cary Swoveland

0

关于差异:

diff_arr = array1.map{|a| a[:name]} - array2.map{|a| a[:name]}

diff_arr.map{|a| array1.map{|s| s if s[:name] == a }}.flatten.compact

交集

intersec_arr = array1.map{|a| a[:name]} & array2.map{|a| a[:name]}

intersec_ar.map{|a| array1.map{|s| s if s[:name] == a }}.flatten.compact

0
所有三个类中引用的Array方法都使用Hash#eql?来比较两个都是哈希的元素。(Hash#eql?检查两个哈希的哈希码是否相等。)因此,我们只需要(暂时)重新定义Hash#eql?即可。
array1 = [{:name =>'Guy1', :age => 45}, {:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1'},             {:name =>'Guy3'}] 

class Hash
  alias old_eql? eql?
  def eql?(h) self[:name] == h[:name] end
end

array1 - array2
  #=> [{:name=>"Guy2", :age=>45}]
array2 - array1
  #=> [{:name=>"Guy3"}]
array1 | array2
  #=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy3"}]
array1 & array2
  #=> [{:name=>"Guy1", :age=>45}]

class Hash
  alias eql? old_eql? # Restore eql?
  undef_method :old_eql?
end

请注意,这是少数几种情况之一,其中必须明确使用self。如果我们写成:
[:name] == h[:name]

改为:

self[:name] == h[:name]

Ruby 会认为我们正在比较数组 [:name]h[:name]


2
我不建议使用这种方法,因为它会对核心类Hash进行猴子补丁。但是,如果OP创建一个新的类,例如class Guys < Hash并覆盖eql?,那么我会非常推荐这种方法!(也许你可以编辑/修改这个答案?) - Mark Thomas
还有,这个不起作用。我认为你忘记了什么(请参见我的答案的提示)。 - Mark Thomas
好观点,@Mark。我主要提出这个解决方案是因为我认为它具有教育价值。在编写它时,我知道自己处于危险之中;我应该至少添加一些警示语。即使是子类化Hash也可能很危险——正如我将在对你的答案的评论中提到的那样——但比修改Hash要安全得多。我将在另一个评论中回答你的第二个观点。 - Cary Swoveland
@Mark,我注意到所有三种方法 [Array#-]()Array#|Array#& 的文档都提到了“为了提高效率,它使用哈希和 eql? 方法来比较元素。” 我知道 Hash.eql? 使用了 Hash#hash,但对于这些 Array 方法会依赖于 Hash#hash(以及 Hash.eql?),这对我来说不太合理,而且我也懒得查看源代码。 我得出结论,这句话只是表述不当。测试结果没有重新定义 Hash#hash 也能通过。你(或其他人)知道是否需要重新定义 Hash#hash,如果需要,为什么? - Cary Swoveland
是的,需要重新定义Hash#hash。在1.9.3 irb中,没有其他方法可以让它正常工作。这里有一个参考链接:http://javieracero.com/blog/the-key-to-ruby-hashes-is-eql-hash - Mark Thomas

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