在结构上使用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 Lisp和SBCL都试过这个,并且所有结构的两个返回(不同)常量(SBCL中的相同类型)。在链接的sxhash
Hyperspec页面上,有以下语句:
对于任何两个对象,x和y,它们都是位向量,字符,圆锥,数字,路径名,字符串或符号,以及 类似的,(sxhash x)和(sxhash y)产生相同的数学 即使x和y存在于不同的Lisp图像中也是如此 实现。请参见第3.2.4节(编译文件中的文字对象)。
- 醇>
对象的哈希码在单个会话中始终相同,前提是该对象未被明显修改 等价测试相等。请参见第18.1.2节(修改哈希 表键)。
后一种说法没有具体说明,但似乎暗示,两个不是equal
的结构将具有不同的哈希码(模数碰撞)是明智的。但是,第一段清单中的结构是可疑的。起初我把它归结为Allegro Lisp中的一个错误,但现在我在两个不同的实现中看到它,我认为必须有一些我不理解的规范。
答案 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中返回的结果是通过散列结构类型的名称来确定的。