首页 > 解决方案 > 带有自定义类的 Ruby Set 等于基本字符串

问题描述

我希望能够在我的集合中找到一个自定义类,只需一个字符串。像这样:

require 'set'

Rank = Struct.new(:name, keyword_init: true) {
  def hash
    name.hash
  end
  def eql?(other)
    hash == other.hash
  end
  def ==(other)
    hash == other.hash
  end
}
one = Rank.new(name: "one")
two = Rank.new(name: "two")
set = Set[one, two]

但是 whileone == "one"one.eql?("one")都是真的,set.include?("one")仍然是假的。我错过了什么?

谢谢!

标签: ruby

解决方案


Set建立在 之上Hash,并且在以下情况下Hash认为两个对象相同:

[...] 它们的hash值是相同的,并且这两个对象是eql?相互关联的。

你缺少的是那eql?不一定是可交换的。识别Rank#eql?字符串不会改变工作方式String#eql?

one.eql?('one') #=> true
'one'.eql?(one) #=> false

因此,它取决于哪个对象是散列键,哪个是 的参数include?

Set['one'].include?(one) #=> true
Set[one].include?('one') #=> false

为了制作两个对象ab可互换的哈希键,必须满足 3 个条件:

  1. a.hash == b.hash
  2. a.eql?(b) == true
  3. b.eql?(a) == true

但是不要尝试修改String#eql?——不建议摆弄 Ruby 的核心类,而且猴子补丁可能无论如何也不起作用,因为 Ruby 通常出于性能原因直接调用 C 方法。

事实上,一开始就同时制作hasheql?模仿name似乎不是一个好主意。它使对象的身份模棱两可,这可能导致非常奇怪的行为并且难以找到错误:

h = { one => 1, 'one' => 1 }
#=> {#<struct Rank name="one">=>1, "one"=>1}

# vs

h = { 'one' => 1, one => 1 }
#=> {"one"=>1}

推荐阅读