在Common Lisp中使用可变数据作为哈希表键



Common Lisp似乎允许将可变数据用作哈希表键。

(defparameter *dict* (make-hash-table))
(defparameter *a* (make-hash-table))
(setf (gethash *a* *dict*) 5)
(loop for key being the hash-keys of *dict*
do (progn
(print key)
(print (gethash key *dict*))))

在这里,一个哈希表被用作另一哈希表中的键。

我对这种行为有点困惑。我的理解是,如果密钥对象发生了变异,那么可变的密钥可能会干扰哈希。

哈希表是如何保持其完整性的,更重要的是,在CL中处理可变哈希表键时,有什么需要知道的吗?这是应该避免的吗?

参见18.1.2修改哈希表密钥:

在等价测试中,如果存在一组对象(或潜在对象(,这些对象在修改前与对象等价,但在修改后不再等价,则对象被明显修改。

如果对象O1被用作哈希表H中的密钥,并且随后关于H的等价性测试被明显地修改,则如果O1或等价性测试下(在修改之前或之后(的等价于O1的任何对象O2被用作对H的进一步操作中的密钥则后果是未指定的。使用O1作为密钥的后果是未指定的,即使O1被明显地修改,然后以撤消可见修改的方式再次修改。

在您的示例中,哈希表测试是eql,这意味着修改密钥(向*a*添加元素(不会改变散列码(即这是而不是"可见修改"(:

(defparameter *ht-1* (make-hash-table :test 'eql))
(defparameter *key* (cons nil nil))
(setf (gethash *key* *ht-1*) 10)
*ht-1*
==> #S(HASH-TABLE :TEST FASTHASH-EQL ((NIL) . 10))
(setf (car *key*) 42)
*ht-1*
==> #S(HASH-TABLE :TEST FASTHASH-EQL ((42) . 10))
(gethash *key* *ht-1*)
==> 10; T
(gethash '(42) *ht-1*)
==> NIL; NIL

因此,您可以看到*ht-1*键控在特定的对象上,而不是任何看起来像它的对象上

另一方面,考虑一个equal哈希表:

(defparameter *ht-2* (make-hash-table :test 'equal))
(setf (gethash *key* *ht-2*) 20)
*ht-2*
==> #S(HASH-TABLE :TEST FASTHASH-EQUAL ((42) . 20))
(gethash *key* *ht-2*)
==> 20; T
(setf (car *key*) 7)            ; **visible modification**!
(gethash '(7) *ht-2*)
==> unspecified!
(gethash *key* *ht-2*)
==> unspecified!
(setf (car *key*) 42)           ; restore key
(gethash '(42) *ht-2*)
==> unspecified!
(gethash *key* *ht-2*)
==> unspecified!

底线是:不要明显地修改哈希表键!

最新更新