【问题标题】:Using CLOS class instances as hash-table keys?使用 CLOS 类实例作为哈希表键?
【发布时间】:2016-02-23 01:01:29
【问题描述】:

我有以下课程:

(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"))

现在,我想使用这个类作为哈希表的键。实例的地址可以很容易地与eq 进行比较。但是问题是,这个category 类可能有多个相同的实例,我希望哈希表也能将其识别为键。

所以,我试图像这样覆盖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 之一的指示符。

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

【问题讨论】:

  • 为什么需要它作为一个类?
  • 您想使用 instances 作为键还是类本身?

标签: common-lisp hashtable equality clos


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    (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 并返回 相同的 对象,您可以安全地将其用作哈希表键:

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

    【讨论】:

    • 我会记住的,很好的方法。
    • 我认为这是一个优雅的解决我的问题的方法。谢谢!
    • 这并不能真正回答问题。它只是表明您可以将槽映射到 conses,equalp 哈希表可以理解。但我最好的猜测是 op 意识到了这一点,通过提到结构的使用,equalp 哈希表也可以理解。
    • @Paulo 这并没有真正回答这个问题。它没有解释一种获取基于槽值散列的散列表的方法,但它是一个答案到“如何使用 clos 实例作为哈希表键?”这个答案不是唯一的,而是“确保你的等效实例是 eq”。一种方法是让他们实习。这不是唯一的解决方案,但在许多情况下它很有用。
    • @JoshuaTaylor,我同意,我没有投反对票。但是鉴于这个问题,这是不可概括的,所以在我看来,它不应该是公认的答案。并且它在没有弱哈希表的情况下泄漏。
    【解决方案3】:
    1. 不要将数字与eq 进行比较,请使用eql=。来自eq(强调我的):

      打印时看起来相同的对象不一定彼此相等。 [...] 允许实现随时“复制”字符和数字。结果是 Common Lisp 不保证 eq 为真,即使它的两个参数都是“相同的东西”,如果那个东西是一个 字符数字。

    2. 您可以使用genhash 库。首先,为您的类型定义一个新的散列函数(另见sxhash)和一个测试函数,并将它与一个测试指示符相关联:

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

      然后,您可以定义一个新表:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-19
      • 1970-01-01
      • 2016-11-30
      • 1970-01-01
      • 2011-07-01
      • 1970-01-01
      • 2011-12-03
      • 1970-01-01
      相关资源
      最近更新 更多