为什么`sxhash`为所有结构返回一个常量?

时间:2014-01-14 23:52:49

标签: struct lisp common-lisp

在结构上使用Common Lisp sxhash函数时,我获得了所有结构的相同值(在SBCL中只有相同类型的结构)。例如,以下代码打印两个整数列表,所有这些整数都具有相同的值。

(progn 
  (defstruct foo 
    data)
  (print (mapcar #'sxhash (loop for i below 10 collect (make-foo :data i))))
  (defstruct bar 
    data)
  (print (mapcar #'sxhash (loop for i below 10 collect (make-bar :data i)))))

 ;;; Allegro
 (319 319 319 319 319 319 319 319 319 319) 
 (319 319 319 319 319 319 319 319 319 319) 
 ;;; SBCL
 (22591133455133788 22591133455133788 22591133455133788 22591133455133788
 22591133455133788 22591133455133788 22591133455133788 22591133455133788
 22591133455133788 22591133455133788) 
(21321591953876048 21321591953876048 21321591953876048 21321591953876048
 21321591953876048 21321591953876048 21321591953876048 21321591953876048
 21321591953876048 21321591953876048) 

我在Allegro LispSBCL都试过这个,并且所有结构的两个返回(不同)常量(SBCL中的相同类型)。在链接的sxhash Hyperspec页面上,有以下语句:

  
      
  1. 对于任何两个对象,x和y,它们都是位向量,字符,圆锥,数字,路径名,字符串或符号,以及   类似的,(sxhash x)和(sxhash y)产生相同的数学   即使x和y存在于不同的Lisp图像中也是如此   实现。请参见第3.2.4节(编译文件中的文字对象)。

  2.   
  3. 对象的哈希码在单个会话中始终相同,前提是该对象未被明显修改   等价测试相等。请参见第18.1.2节(修改哈希   表键)。

  4.   

后一种说法没有具体说明,但似乎暗示,两个不是equal的结构将具有不同的哈希码(模数碰撞)是明智的。但是,第一段清单中的结构是可疑的。起初我把它归结为Allegro Lisp中的一个错误,但现在我在两个不同的实现中看到它,我认为必须有一些我不理解的规范。

2 个答案:

答案 0 :(得分:6)

我询问弗兰兹的支持,这是他们的回应。据推测,SBCL正在做类似的事情。

  

函数cl:sxhash始终为结构返回相同的值   对象。原因是它没有额外的存储空间   其中的唯一哈希码。结果,使用结构作为键   是非常低效的。 excl :: hash-table-stats函数演示   当给定一个带有用作键的结构的哈希表时;直方图   成为最糟糕的情况,因为每个键都需要相同的索引。

     

决定保持结构对象的相同行为,   因为在所有结构中自动包含散列槽   对象会使所有结构平均延长一个单词。对于   小结构这对我们的许多用户来说是不可接受的。

     

相反,用户可以定义带有额外插槽的结构,以及   该结构类型的构造函数可以在其中存储唯一值   slot(随机值或通过递增a得到的值)   每次运行构造函数时计数器)。另外,创建一个哈希   生成访问该哈希槽以生成其的函数   值。如果要进行散列的结构被隐藏在列表中,那么   这个哈希函数需要知道如何遍历这些键   获得一个独特的价值。最后,使用。构建哈希表   记录:make-hash-table的哈希函数参数(仍然使用   相等的测试参数),创建一个哈希表   分布均匀。

     

或者,如果你可以保证你的所有插槽都没有   结构在被用作密钥后会被更改   hash-table,你可以使用你的equp测试函数   make-hash-table调用,而不是相等。但是,如果你这样做的话   确保这些结构对象不会改变,因为它们可能不会   可以在哈希表中找到。

答案 1 :(得分:1)

sxhash有两个重要属性:如果(equal x y)那么(= (sxhash x) (sxhash y))sxhash返回的值对于任何对象都是相同的(即使在lisp图像之间)

现在结构是equal,只要它们是eq(即它们具有相同的地址),但sxhash不能简单地返回结构的地址(或其某些散列),因为由于垃圾回收,地址可能会发生变化。因此,在设计lisp实现时,必须选择是否让sxhash对于每个结构都相同,或者在每个结构中存储一些标识,这些标识在垃圾收集器移动结构时不会改变,因此可以用于{{ 1}}对象。大多数实现(包括Franz和sbcl)都考虑添加这样的值,如果只给它一些备用位,则浪费空间或无用。

这种权衡最终只会影响用户实现哈希表的尝试,因为实现自己的哈希表可以使用对象的地址并通知垃圾收集器,这样他们就可以在对象移动时重新进行哈希表示(我不知道& #39;不知道任何实现是否这样做)。某些实现(包括sbcl)允许您使用自己的比较/散列操作自定义内置散列表。也许如果你自己实现了哈希,你可以为结构添加一个额外的字段。

我认为sxhash在sbcl中返回的结果是通过散列结构类型的名称来确定的。