Lisp:使用语法糖访问递归哈希



我正在尝试构建一个函数(或宏(来简化哈希表中数据深度的获取和设置(意思是哈希中的哈希,哈希中的哈希等(。我认为我不能用宏做到这一点,而且我不确定如何使用 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*))

更新 - 我去吧:

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

嵌套表查找不需要任何花哨的东西。

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"

它是带有访问库的单行:

(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,hash table,objects,...(。对于嵌套访问,我们使用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-consistent-access-of-data-structures-dotted-path/

对于临时构造,您可以使用 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)))

如果所有键都存在,这应该可以工作,gethash不能直接在reduce中使用的唯一原因是它以不正确的顺序接收输入以正常工作。

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

当然,这有局限性,不会创建不存在的哈希。

最新更新