【问题标题】:Ruby Hash check is subset?Ruby Hash 检查是子集?
【发布时间】:2014-06-01 21:26:58
【问题描述】:

如何判断 Ruby 哈希是否是(或包含)另一个哈希的子集?

例如:

hash = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}
hash.include_hash?({})            # true
hash.include_hash?({f: 6, c: 3})  # true
hash.include_hash?({f: 6, c: 1})  # false

【问题讨论】:

  • 马特,你似乎被授予了embarrassment of riches。 (英语是第二语言的人的链接。)在 SO 上获得四分之四的高质量答案非常罕见。

标签: ruby hash subset contain


【解决方案1】:

从 Ruby 2.3 开始,您还可以执行以下操作来检查这是否是子集

hash = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}
{} <= hash           # true
{f: 6, c: 3} <= hash # true
{f: 6, c: 1} <= hash # false

【讨论】:

  • 这是否适用于嵌套哈希?否则,这绝对是一个简洁的答案。
  • @nus 它确实适用于嵌套哈希。但是,嵌套散列必须相等,因此不能只是一个子集:{a: {x:1, y:2}} &lt;= {a: {x:1, y:2}, b:3 } # true{a: {x:1}} &lt;= {a: {x:1, y:2}, b:3 } # false
  • 反之亦然:hash &gt;= {f: 6, c: 3}
【解决方案2】:

我想到的解决方案使用Hash#merge方法:

class Hash
  def include_hash?(hash)
    merge(hash) == self
  end
end

hash = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}
hash.include_hash?({})
# => true
hash.include_hash?(f: 6, c:3)
# => true
hash.include_hash?(f: 6, c:1)
# => false

【讨论】:

  • 对于嵌套哈希,您可以使用active support's deep_merge
  • 这很优雅。我打电话给我的superset?,我也打电话给subset?。
  • 如果您希望构建断言以测试 RSpec 中的嵌套哈希值,您可以使用 Rspec 的 includes 匹配器。见stackoverflow.com/a/29248139/590767。在测试中使用include_hash? 只会打印出真假,并且信息量比includes 方法少。
【解决方案3】:

数组差异似乎最简单:

class Hash
  def include_hash?(h)
    (h.to_a - to_a).empty?
  end
end

h = {a: 1, b: 2}
h.include_hash?({b: 2}) #=> true
h.include_hash?({b: 3}) #=> false

【讨论】:

  • 我理解(h.to_a - to_a).. 但为了更好的可读性(h.to_a - self.to_a)... IMO
  • @Arup,我非常不同意,尽管我知道您的观点得到了许多人的认同。 Imo,self.to_a 建议这里需要self(如果它后面跟着一个访问器或class)。我认为最好避免在不需要时使用self。如果读者对to_a 没有self 感到困惑,他们会很快弄清楚,并从中学到一些东西。
【解决方案4】:

您可以将哈希值转换为sets,然后使用subset? 和superset?(或它们各自的别名&lt;= 和&gt;=)方法执行检查:

require 'set'

hash.to_set.superset?({}.to_set)
# => true

hash.to_set >= {a: 1}.to_set
# => true

{a: 2}.to_set <= hash.to_set 
# => false

更新:建议解决方案的基准:

require 'fruity'
require 'set'

hash = ('aa'..'zz').zip('aa'..'zz').to_h
# {"aa"=>"aa", "ab"=>"ab", ...
find = ('aa'..'zz').zip('aa'..'zz').select { |k, _| k[0] == k[1] }.to_h 
# {"aa"=>"aa", "bb"=>"bb", ...

compare(
  toro2k:        -> { hash.to_set >= find.to_set },
  MarekLipka:    -> { hash.merge(find) == hash },
  CarySwoveland: -> { (find.to_a - hash.to_a).empty? },
  ArupRakshit:   -> { arr = hash.to_a; find.all? { |pair| arr.include?(pair) } }
)

结果:

Running each test 2 times. Test will take about 1 second.
MarekLipka is faster than toro2k by 3x ± 0.1
toro2k is faster than CarySwoveland by 39.99999999999999% ± 10.0%
CarySwoveland is faster than ArupRakshit by 1.9x ± 0.1

【讨论】:

  • 谢谢!基于Set#superset?,我应该调用方法Hash#superset?而不是Hash#include_hash?。
  • superset? 可能是一个更好的名字,但我认为这只是个人喜好问题。
  • 哇,我只想说谢谢你把我介绍给fruity gem!太棒了。
  • 很好的解决方案,toro,感谢您提供丰富的基准。 (我从没听说过水果味,直到我读到@Nick 的评论才注意到它的要求)。
【解决方案5】:

你可以这样做:

def include_hash?(hash,subset_hash)
   arr = hash.to_a
   subset_hash.all? { |pair| arr.include?(pair) }
end

【讨论】:

  • 任何人都可以检查subset_hash.all? { |pair| arr.include?(pair) }是否也在工作...我不在电脑旁,所以无法测试...
  • 我检查了一些测试用例,它似乎工作正常。
  • 它读起来很好——很容易快速理解你在做什么。
猜你喜欢
  • 2018-03-18
  • 1970-01-01
  • 2015-03-20
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 2020-12-18
  • 2012-12-20
  • 2013-08-13
相关资源
最近更新 更多