Using CLOS class instances as hash-table keys?

孤人 提交于 2019-12-07 03:38:57

问题


I have the following 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"))

Now, I would like to use this class as a key for a hash-table. The addresses of instances can be easily compared with eq. The problem is however, there might be multiple identical instances of this category class and I would like the hash-table to recognize this as a key as well.

So, I was trying to overwrite the :test argument of the make-hash-table function like this:

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

Unfortunately, this is not allowed. :test needs to be a designator for one of the functions eq, eql, equal, or equalp.

One way to solve this would be to turn the class category into a struct, but I need it to be a class. Is there any way I can solve this?


回答1:


You can use a more extensible hash table library, as explained in coredump's answer, but you could also use the approach that Common Lisp takes toward symbols: you can intern them. In this case, you just need an appropriate interning function that takes enough of a category to produce a canonical instance, and a hash table to store them. E.g., with a simplified 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))))))

Then, you can call intern-category with the same arguments and get the same object back, which you can safely use as a hash table key:

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



回答2:


  1. Don't compare numbers with eq, use eql or =. From eq (emphasis mine):

    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. You can use the genhash library. First, you define a new hash function (see also sxhash) and a test function for your type and you associate it with a test designator:

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

    Then, you can define a new table:

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



回答3:


Many Common Lisp implementations provide extensions to the ANSI Common Lisp standard to support different test and hash functions (and a lot more).

CL-CUSTOM-HASH-TABLE is a compatibility layer.



来源:https://stackoverflow.com/questions/33828408/using-clos-class-instances-as-hash-table-keys

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!