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!
底线是:不要明显地修改哈希表键!