【问题标题】:How do to refine how to select key for hash?如何细化如何选择哈希键?
【发布时间】:2021-06-01 04:05:14
【问题描述】:

所以,我正在尝试修补一个类以允许它用于哈希键。但是,我似乎无法让它工作。这是一个示例测试用例:

class Tmp
 def hash; rand(); end
end

module Patch
 refine Tmp do
  def hash; 1; end
  def eql?(rhs); hash == rhs.hash; end
 end
end

puts "=========================="
module A
 # Using the initial implementation of Tmp
 x = {}
 x[Tmp.new] = 1
 x[Tmp.new] = 2
 puts "#{Tmp.new.hash == Tmp.new.hash}"
 puts "#{Tmp.new.eql? Tmp.new}"
 puts "#{x}"
end

puts "=========================="
module A
 # Enabling the refinement of class Tmp here
 using Patch
 x = {}
 x[Tmp.new] = 1
 x[Tmp.new] = 2
 puts "#{Tmp.new.hash == Tmp.new.hash}"
 puts "#{Tmp.new.eql? Tmp.new}"
 puts "#{x}"
end

这给出了结果:

==========================
false
false
{#<Tmp:0x0000407e4ae818>=>1, #<Tmp:0x0000407e4ae7a0>=>2}
==========================
true
true
{#<Tmp:0x0000407e4ae188>=>1, #<Tmp:0x0000407e4ae110>=>2}

如您所见,#eql?#hash 方法按预期工作,但是当我尝试将其用作哈希键时,它认为它们是不同的键。我做错了什么?

编辑

顺便说一句,docs 状态:

两个对象引用相同的哈希键当它们的哈希值相同时并且这两个对象是eql?彼此。

如果 hash 和 eql?方法被覆盖以提供有意义的行为。

(强调我的)

有趣的是,似乎只有#eql? 用于确定密钥相等性:

class Tmp
 def hash; rand; end
 def eql?(rhs); true; end
end

puts "=========================="
module A
 x = {}
 x[Tmp.new] = 1
 x[Tmp.new] = 2
 puts "#{Tmp.new.hash == Tmp.new.hash}"
 puts "#{Tmp.new.eql? Tmp.new}"
 puts "#{x}"
end

给予:

==========================
false
true
{#<Tmp:0x0000407e407b08>=>2}

#hash 的返回值不相关:

class Tmp
 def hash; 1; end
 def eql?(rhs); false; end
end

puts "=========================="
module A
 x = {}
 x[Tmp.new] = 1
 x[Tmp.new] = 2
 puts "#{Tmp.new.hash == Tmp.new.hash}"
 puts "#{Tmp.new.eql? Tmp.new}"
 puts "#{x}"
end

给予:

==========================
true
false
{#<Tmp:0x0000407e40e408>=>1, #<Tmp:0x0000407e40e3b8>=>2}

这似乎与文档相矛盾。这是使用ruby v2.2.4的神器吗? (我使用的是 Sketchup 2017 自带的 ruby​​)

【问题讨论】:

  • 注意:有一个随机的hash 值会导致问题。每次在存储相同数据的同一个对象上调用它时都需要返回相同的值。
  • @tadman 我使用rand 仅用于示例目的。我通常不会这样做。
  • 是的,只是想弄清楚这里的意图。
  • 读了几遍后,我仍然不确定问题是什么。最后一个示例按预期工作,您已强制它们不是eql?,因此您获得了两个单独的键。
  • @tadman,是的,但我希望最后一个示例也应该与最后一个示例具有相同的结果,因为#hash 没有给出恒定值,即使#eql? 给出true.

标签: ruby


【解决方案1】:

您正在以明显不一致的行为实现您的课程。对于a.eql?(b) 的任何对象,然后根据定义a.hash == b.hash#hash 必须反映对象的实际内容。如果不是这样,你会得到非常奇怪的结果。

就像在编写排序比较器时,如果a &lt; bb &lt; c 然后a &lt; c 必须为真。如果由于某种原因这不是真的,那么您的排序操作将产生非常奇怪和不一致的结果。

换句话说:

  • a.eql?(b) 必须为真当且仅当ab 代表相同的事物。
  • 如果ab 代表相同的事物,a.hash == b.hash 必须为真。

请注意,a.hash == b.hash 并不意味着它们一定是相同的东西,但它表明它们很有可能是相同的。始终需要通过eql? 进行二次检查以确认这一点。

哈希内部可能会也可能不会调用这两个函数。在小尺寸下,Ruby“哈希”根本不是哈希表,它只是一个小数组。因此,#hash 甚至可能不会被调用,直到表达到特定大小并且该方法是相关的。

【讨论】:

  • 您似乎没有抓住重点。后半部分(编辑后)拍摄的结果与文档不一致,即#eql? #hash 必须为真。鉴于此,显示的 both 示例应该将每个对象视为不同的键。此外,这是一个旁白。就在 Edit 陈述实际问题之前。需要明确的是,为什么不是用于哈希键的细化,而是直接使用方法时。
  • 我已经更新了我的问题,希望能强调适当的点。
  • 我不确定改进是否会在这里发挥作用,因为 Hash 本身调用了这些方法,这超出了改进的范围。
  • 是的,我刚刚意识到这一点。 using 仅限于我使用它的模块或类的范围,并且仅限于从那时起直到该模块或类块关闭。自动转换为字符串时,似乎与#to_s 相同。不幸的。 :(
  • 是的,它在效果上非常本地化,不会影响其他源文件。
猜你喜欢
  • 2019-06-30
  • 1970-01-01
  • 2014-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-30
  • 2014-07-25
  • 2016-11-11
相关资源
最近更新 更多