相当于Clojure在Common lisp中的“assoc-in”和“get-in”

时间:2015-10-12 12:21:17

标签: clojure common-lisp

在Clojure中,您可以使用assoc-in更新地图(dict)并自动创建关键路径(如果它不存在)。

(assoc-in {:a 1 :b 3} [:c :d] 33)
; {:a 1, :c {:d 33}, :b 3}

与get-in相同:您可以指定键的路径(或列表索引),它将返回路径指定的值,如果不存在,则返回nil。

(get-in {:a 1, :c {:d 33}, :b 3} [:c :d])
; 33
(get-in {:a 1, :c {:d 33}, :b 3} [:c :e])
; nil

我喜欢在普通口齿不清中加糖,所以我在一个“联想”中喋喋不休。我用列表结构制作的trie测试了它,我离开':leaf'保留,所以结果是'get-in'总是列出:

  • 测试用例: (setf trie '(:A (:B (:leaf t) :C (:leaf t)) :D (:leaf t)))

get-in实施和测试:

(defmacro get-in (trie ks)  `(reduce #'getf  ,ks :initial-value ,trie))
(get-in trie '(:a :b)) ; (:leaf t)
(get-in trie '(:a :b :leaf)) ; t

关联实施和测试:

(defmacro assoc-in (trie ks v)
 `(setf (getf (get-in ,trie ,ks) :leaf) ,v))

(assoc-in trie '(:a :b) 99)
(get-in trie '(:a :b)) ; (:leaf 99)
(assoc-in trie '(:a :b :new-key) "new-key") ; (SETF REDUCE) is not fbound

我在' assoc-in'有问题,我可以更新trie但无法插入

欢迎任何建议,不必是宏观的。我查了Clojure implementation并尝试在Common lisp中执行此操作,也失败了。

2 个答案:

答案 0 :(得分:2)

我在这里是怎么做到的。代码中的文档字符串和注释解释了发生了什么。首先是效用函数:

(defun make-nested-alist (value items)
  "Returns a nested association list that takes the first item
to an association list that takes the second item to an 
association list, and so on, finally to the value.  If items
is the empty list, then the value is returned by itself."
  (reduce (lambda (item value)
            (acons item value '()))
          items :initial-value value
          :from-end t))

(make-nested-alist 3 '())
;=> 3
(make-nested-alist 3 '(a b c))
;;=> ((a . ((b . ((c . e))))))

现在,一个从嵌套关联列表中检索值的函数。

(defun assoc* (items alist)
  "Finds the item in the nested association list keyed by the items.
It is an error if a prefix of the path leads to a value that cannot be
interpreted as an associate list."
  (if (endp items) alist
      (assoc* (rest items)
              (cdr (assoc (first items) alist)))))
(defparameter *alist*
  (copy-tree '((a . 1)
               (b . 3)
               (c . ((d . 33))))))

(assoc* '(a) *alist*) ;=> 1
(assoc* '(c d) *alist*) ;=> 33
(assoc* '(c e) *alist*) ;=> NIL
(assoc* '(a b) *alist*) ; ERROR (since the prefix (a) leads to 1)

现在,一个功能来更新"嵌套关联列表。请注意,这将更新大多数关联列表,但由于您无法修改空列表,因此需要使用此函数中的返回值

(defun assoc*-update (value items alist)
  "Returns a nested association list like the provided one, except
that the value at the path specified by items contains value.  This
will modify the association list if the any prefix of the path is a
value in the association list.  Because the result may be a new
list (e.g., when the original association list does not have a top
level entry for the initial item in the path), the result should be
used."
  (if (endp items)
      value
      (let ((entry (assoc (first items) alist)))
        (if (null entry)
            ;; if there is no entry at all, then we need to make a
            ;; nested association list out of the rest keys that
            ;; eventually comes back to the value, and tack it onto
            ;; the current alist. We can't modify alist, because alist
            ;; might be empty, and we can't modify the empty list.
            (acons (first items) (make-nested-alist value (rest items)) alist)
            ;; Otherwise, there is an entry, and that takes care of the
            ;; first key, but we'll need to recurse into the value and
            ;; update it.  If there are no keys left, then we should just
            ;; replace this entry, otherwise we need to recurse into it.
            ;; In both cases, we return alist.
            (prog1 alist
              (rplacd entry (assoc*-update value (rest items) (cdr entry))))))))
(let ((alist (copy-tree *alist*)))
  (setf alist (assoc*-update 42 '(c e) alist))
  (print alist)
  (setf alist (assoc*-update 89 '(d) alist))
  (print alist))
;=>          ((A . 1) (B . 3) (C (E . 42) (D . 33))) 
;=> ((D . 89) (A . 1) (B . 3) (C (E . 42) (D . 33))) 

答案 1 :(得分:1)

由于有95次观看,我在这里发布我的解决方案。

(defvar *list-trie* '(:A (:B (:leaf t) :C (:leaf t)) :D (:leaf t)))
(defun get-in-list (trie ks)
  "input a key chain list, return children of common ancestors, return nil of not exist"
  (reduce #'(lambda (node k) (and (listp node) (getf node k))) ks :initial-value trie))

(defmacro clojure-assoc-list (trie k v)
  "return updated list"
  `(progn (setf (getf ,trie ,k) ,v)
          ,trie))

(defun assoc-in-list (trie ks v)
  (if (= 1 (length ks))
      (clojure-assoc-list trie (car ks)  v)
      (clojure-assoc-list trie (car ks) (assoc-in-list (getf trie (car ks)) (cdr ks) v))))