问题
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:
Don't compare numbers with
eq
, useeql
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.
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