Using CLOS class instances as hash table keys?

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. Instance addresses can be easily compared with eq . The problem, however, may be several identical instances of this category class, and I would like the hash table to recognize this as a key.

So, I tried to rewrite the argument :test the make-hash-table function as follows:

 (make-hash-table :test #'(lambda (ab) (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 unacceptable. :test should be a designation for one of the functions eq, eql, equal or equalp.

One way to solve this problem is to turn the category class into a structure, but I need it to be a class. Is there any way to solve this?

+6
source share
3 answers

You can use a more extensible hash table library as described in the coredump answer, but you can also use the approach that Common Lisp takes to characters: you can intern them. In this case, you just need the corresponding interning function, which takes only one category to create a canonical instance and a hash table to store them. For example, 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 the intern-category with the same arguments and return the same object back that can be safely used as a hash table:

 (eq (intern-category "foo" 45) (intern-category "foo" 45)) ;=> T 
+6
source
  • Do not compare numbers with eq , use eql or = . From eq (my attention):

    Objects that appear the same when printed are not necessarily equivalent to each other. [...] Implementations are allowed to make "copies" of characters and numbers at any time. The effect is that Common Lisp makes no guarantee that eq is true , even if both its arguments are β€œthe same”, if this thing is a symbol 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 pointer:

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

    Then you can define a new table:

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

Many common Lisp implementations provide extensions to the ANSI Common Lisp standard to support various test and hash functions (and much more).

CL-CUSTOM-HASH-TABLE is the level of compatibility.

+6
source

All Articles