使用 CLOS class 实例作为 hash-table 键?

Using CLOS class instances as hash-table keys?

我有以下 class:

(defclass category ()
    ((cat-channel-name
    :accessor cat-channel-name :initarg :cat-channel-name :initform "" :type string
    :documentation "Name of the channel of this category")
    (cat-min
    :accessor cat-min :initarg :min :initform 0 :type number
    :documentation "Mininum value of category")
    (cat-max
    :accessor cat-max :initarg :max :initform 1 :type number
    :documentation "Maximum value of category"))
    (:documentation "A category"))

现在,我想使用这个 class 作为散列-table 的键。实例的地址可以很容易地与eq进行比较。然而,问题是,这个 category class 可能有多个相同的实例,我希望 hash-table 也将其识别为一个键。

所以,我试图像这样覆盖 make-hash-table 函数的 :test 参数:

(make-hash-table :test #'(lambda (a b) (and (equal (cat-channel-name a) (cat-channel-name b))
                                            (eq (cat-min a) (cat-min b))
                                            (eq (cat-max a) (cat-max b)))

很遗憾,这是不允许的。 :test 需要是函数 eq、eql、equal 或 equalp 之一的指示符。

解决这个问题的一种方法是将 class category 变成一个结构,但我需要它是一个 class。有什么办法可以解决这个问题吗?

  1. 不要将数字与 eq 进行比较,请使用 eql=。来自 eq(强调我的):

    Objects that appear the same when printed are not necessarily eq to each other. [...] An implementation is permitted to make "copies" of characters and numbers at any time. The effect is that Common Lisp makes no guarantee that eq is true even when both its arguments are "the same thing" if that thing is a character or number.

  2. 您可以使用 genhash library. First, you define a new hash function (see also sxhash) 和您的类型的测试函数,并将其与 测试指示符相关联:

    (genhash:register-test-designator
      'category= 
      (lambda (category) <hashing>)
      (lambda (a b) 
        (and (equal ... ...)
             (= ... ...)
             (= ... ...))))
    

    然后,你可以定义一个新的table:

    (genhash:make-generic-hashtable :test 'category=)
    

您可以使用更具扩展性的散列 table 库,如 coredump 的回答中所述,但您也可以使用 Common Lisp 对符号采用的方法:您可以 intern 他们。在这种情况下,您只需要一个适当的实习函数,该函数采用足够的类别来生成规范实例,并使用散列 table 来存储它们。例如,使用简化的 category class:

(defclass category ()
  ((name :accessor cat-name :initarg :name)
   (number :accessor cat-number :initarg :number)))

(defparameter *categories*
  (make-hash-table :test 'equalp))

(defun intern-category (name number)
  (let ((key (list name number)))
    (multiple-value-bind (category presentp)
        (gethash key *categories*)
      (if presentp category
          (setf (gethash key *categories*)
                (make-instance 'category
                               :name name
                               :number number))))))

然后,您可以使用相同的参数调用 intern-category 并返回 相同的 对象,您可以安全地将其用作哈希 table 键:

(eq (intern-category "foo" 45)
    (intern-category "foo" 45))
;=> T

许多 Common Lisp 实现提供了对 ANSI Common Lisp 标准的扩展,以支持不同的测试和哈希函数(以及更多)。

CL-CUSTOM-HASH-TABLE是兼容层。