Лисп: Доступ к рекурсивным хэшам с синтаксическим сахаром - PullRequest
2 голосов
/ 14 июня 2019

Я пытаюсь построить функцию (или макрос), чтобы упростить получение и настройку данных глубоко в хеш-таблице (то есть хеш-код внутри хеш-кода, внутри хеш-кода и т. Д.).Я не думаю, что смогу сделать это с помощью макроса, и я не уверен, как это сделать с помощью eval.Я хотел бы иметь возможность сделать следующее:

(gethashdeep *HEROES* "Avengers" "Retired" "Tony Stark")

и получить это возвращение "Железный человек"

Все хэши создаются с:

(setf hashtablename (make-hash-table :test 'equal))

и заполняется оттуда.

Я могу сделать следующее, но хотел бы абстрагировать его, чтобы я мог программно извлечь значение с произвольной глубины:

;;pulling from a hash that's 2 deep
(gethash "Tony Stark" (gethash "Avengers" *HEROES*))

update - мой путь вэто:

(defun getdeephash (hashpath h k)
  (let* ((rhashpath (reverse hashpath))
    (hashdepth (list-length hashpath))
    (hashcommand (concatenate 'string "(gethash \"" k "\"")))
   (loop for i from 1 to hashdepth
      do (setf hashcommand (concatenate 'string hashcommand "(gethash \"" (nth (- i 1) rhashpath) "\"")))
      (setf hashcommand (concatenate 'string  hashcommand " "  h (make-string (- hashdepth 0) :initial-element #\Right_Parenthesis) ")"))
      (values hashcommand)))

Ответы [ 4 ]

8 голосов
/ 14 июня 2019

Это один вкладыш с библиотекой Access :

(ql:quickload "access")

Мы определяем хеш-таблицу *heroes* (как в примере Xach):

(defun table (&rest keys-and-values &key &allow-other-keys)
  (let ((table (make-hash-table :test 'equal)))
    (loop for (key value) on keys-and-values by #'cddr
          do (setf (gethash key table) value))
    table))
TABLE

(defparameter *heroes*
  (table "Avengers"
         (table "Retired" (table "Tony Stark" "Iron Man")
                "Active" (table "Bruce Banner" "Hulk"))))

Обычно мы используем access:access для согласованного доступа к различным структурам данных (alist, plist, хеш-таблица, объекты и т. Д.). Для вложенного доступа мы используем access:accesses (множественное число):

(access:accesses *heroes* "Avengers" "Retired" "Tony Stark")
"Iron Man"

Кроме того, мы можем setf это:

(setf (access:accesses *heroes* "Avengers" "Retired" "Tony Stark") "me")
"me"

Это библиотека, проверенная в бою, поскольку она является ядром библиотеки шаблонов Djula, одной из самых загружаемых библиотек Quicklisp.

мой пост в блоге: https://lisp -journey.gitlab.io / blog / generice-последовательность-access-of-data-структуры-dot--path /

8 голосов
/ 14 июня 2019

Для поиска по вложенным таблицам нет ничего особенного.

Одно возможное определение gethash-deep:

(defun gethash-deep (value &rest keys)
  (if (or (endp keys)
          (not (hash-table-p value)))
      value
      (apply #'gethash-deep
             (gethash (first keys) value)
             (rest keys))))

Пример использования:

(defun table (&rest keys-and-values &key &allow-other-keys)
  (let ((table (make-hash-table :test 'equal)))
    (loop for (key value) on keys-and-values by #'cddr
          do (setf (gethash key table) value))
    table))

(defparameter *heroes*
  (table "Avengers"
         (table "Retired" (table "Tony Stark" "Iron Man")
                "Active" (table "Bruce Banner" "Hulk"))))

(gethash-deep *heroes* "Avengers" "Retired" "Tony Stark") => "Iron Man"
(gethash-deep *heroes* "Avengers" "Active" "Bruce Banner") => "Hulk"
4 голосов
/ 14 июня 2019

Для специальной конструкции вы можете использовать ->> из arrows:

(->> table
     (gethash "Avengers")
     (gethash "Retired")
     (gethash "Tony Stark"))

Если вы хотите смешать с другими средствами доступа (например, aref), вы можете использовать as-> или -<> вместо того, чтобы иметь дело с различными порядками аргументов.

Если бы я хотел реализовать это, я бы положился на неявные проверки и, возможно, использовал бы reduce:

(defun gethash-deep (hash-table &rest keys)
  (reduce (lambda (table key)
            (gethash key table))
          keys
          :initial-value hash-table))

Или loop:

(defun gethash-deep (table &rest keys)
  (loop :for k :in keys
        :for v := (gethash k table) :then (gethash k v)
        :finally (return v)))
0 голосов
/ 29 июня 2019

Если все ключи существуют, это должно работать, единственная причина, по которой gethash не может быть использована непосредственно в редукте, состоит в том, что он получает входные данные в неправильном порядке для правильной работы.

(defun gethashdeep (table &rest keys)
  (reduce #'(lambda (h k)
          (gethash k h)) (cdr keys)
          :initial-value (gethash (car keys) table)))

Конечно, это может быть написано как макрос и может быть установлено

(defmacro gethashdeep1 (table &rest keys)
  (reduce #'(lambda (h k) (list 'gethash k h)) (cdr keys)
          :initial-value (list 'gethash (car keys) table)))

Конечно, это имеет ограничения и не создает хэши, которые не существуют.

...