如何为包含可比较的类定义哈希函数?

时间:2014-03-08 22:46:25

标签: ruby hash comparable

想象一下这样的课程:

class Element
  include Comparable
  attr_accessor :name, :pos_x, :pos_y

  def initialize(name, pos_x, pos_y)
    @name = name
    @pos_x = pos_x
    @pos_y = pos_y
  end

  def <=>(other)
    if (@pos_x == other.pos_x) and (@pos_y == other.pos_y)
      return 0
    else 
      return @name <=> other.name
    end
  end

  def eql?(other)
    self == other
  end
end

在这种情况下,如何实现hash函数a.hash == b.hash?一般来说,我会这样做:

def hash
  @name.hash
end

但这不包括pos_xpos_y

3 个答案:

答案 0 :(得分:1)

不幸的是,在这种情况下,在数学上不可能定义有效的散列函数。

设a,b是两个具有相同位置和不同名称的元素。根据{{​​1}}定义,这意味着eql?。由于任何名称值都是如此,因此哈希函数与name属性无关,但这与第二次检查相矛盾。因此,此h(a) == h(b)定义没有散列函数。抱歉。 :(

更新

如toro2k所述 - 您的等式定义不具有传递性。通常,如果a == b和b == c,则需要a == c。根据您的eql?功能:

eql?

{pos_x: 1, pos_y: 1, name: 'a'} == {pos_x: 1, pos_y: 1, name: 'b'}
{pos_x: 1, pos_y: 1, name: 'b'} == {pos_x: 2, pos_y: 2, name: 'b'}

这是你问题的根源。

答案 1 :(得分:1)

除了你已经在做的事情之外,你无能为力。请注意,您的hash函数非常糟糕,因为它的分布很差并且会导致大量冲突,这可能会破坏Hash的O(1)摊销的最坏情况步骤复杂度保证,但你真的无能为力。

但请注意,您的等式和排序定义存在更大的问题:它们不是相等或排序!

平等和订购关系需要

  • 反身:a~a
  • 对称:a〜b&lt; =&gt; b~a
  • 传递:a~b&amp;&amp; b~c =&gt; a~c

你的平等和秩序关系都违反了传递性:

one   = Element.new('Z', 1, 1)
two   = Element.new('A', 1, 1)
three = Element.new('A', 2, 2)

one == two   # => true
two == three # => true
one == three # => false # Huh?

one <= two   # => true
two <= three # => true
one <= three # => false # Huh?

使用太空船运营商的所有方法(例如Enumerable#sort)都依赖于这些法律。因此,您的<=>eql?实施从根本上被打破了。

答案 2 :(得分:0)

您可以覆盖==,因为Object#hash不接受参数:

def ==(o)
  (@pos_x == o.pos_x && pos_y == o.pos_y) || (@name == o.name)
end

alias_method :eql?, :==