Использование экземпляров класса CLOS в качестве ключей хеш-таблицы?

У меня есть следующий класс:

(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, и я хотел бы, чтобы хеш-таблица также распознавала это как ключ.

Итак, я пытался перезаписать аргумент :test функции make-hash-table следующим образом:

(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 в структуру, но мне нужно, чтобы это был класс. Есть ли способ решить эту проблему?


person JNevens    schedule 20.11.2015    source источник
comment
Зачем вам это нужно, чтобы быть классом?   -  person coredump    schedule 20.11.2015
comment
Вы хотите использовать экземпляры в качестве ключей или сам класс?   -  person Joshua Taylor    schedule 20.11.2015


Ответы (3)


Вы можете использовать более расширяемую библиотеку хеш-таблиц, как объяснено в ответе 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
person Joshua Taylor    schedule 20.11.2015
comment
Я запомню об этом, хороший подход. - person coredump; 20.11.2015
comment
Я думаю, что это элегантное решение моей проблемы. Спасибо! - person JNevens; 21.11.2015
comment
Это на самом деле не отвечает на вопрос. Это просто показывает, что вы можете сопоставить слоты с conses, которые equalp хэш-таблицы могут найти. Но мне кажется, что оператор знает об этом, упомянув об использовании структур, которые equalp хэш-таблицы могут обрабатывать точно так же. - person acelent; 24.11.2015
comment
@Paulo Это на самом деле не отвечает на вопрос. это не объясняет способ получения хеш-таблицы, которая хэширует на основе значений слотов, но это ответ на то, как я могу использовать экземпляры clos в качестве ключи хеш-таблицы? Этот ответ не единственный, но убедитесь, что ваши эквивалентные экземпляры равны. И способ сделать это интернировать их. Это не единственное решение, но во многих случаях оно полезно. - person Joshua Taylor; 24.11.2015
comment
@JoshuaTaylor, я согласен, я не минусовал. Но это нельзя обобщить, учитывая вопрос, поэтому, на мой взгляд, это не должен быть принятый ответ. И утекает без слабых хеш-таблиц. - person acelent; 24.11.2015

Многие реализации Common Lisp предоставляют расширения стандарта ANSI Common Lisp для поддержки различных тестовых и хеш-функций (и многого другого).

CL-CUSTOM-HASH-TABLE — это уровень совместимости.

person Rainer Joswig    schedule 20.11.2015

  1. #P1# <блочная цитата> #P2#
  2. Вы можете использовать библиотеку genhash. Сначала вы определяете новую хеш-функцию (см. также sxhash) и тестовую функцию для ваш тип и вы связываете его с назначением теста:

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

    Затем вы можете определить новую таблицу:

    (genhash:make-generic-hashtable :test 'category=)
    
person coredump    schedule 20.11.2015