Common Lisp嵌套结构更改

时间:2017-09-12 21:29:01

标签: common-lisp

我正在学习Common Lisp,并尝试将一些JSON发送到Web服务。我要发送的JSON以类似于以下内容的结构表示:

((:ITEMS
  ((:KEY . "value1") (:IGNORE 1 2 3))
  ((:KEY . "value2") (:IGNORE 1 2 3))))

我想从:IGNORE中的每个项目中删除密钥:ITEMS

在Clojure中(我更熟悉)我会使用update将功能更改应用于键:ITEMS,映射每个项目,然后使用select-keysdissoc获得我需要的结构。但是,我无法找到在Common Lisp中执行此修改的正确方法。

我应该使用copy-tree复制结构,然后使用setf更改树吗?

NB1:我不确定我使用的数据结构是否“正常”,但这是我从CL-Json包调用json:decode-json-from-string时返回的结果。

到目前为止,我有这个

(SETF (CDR (ASSOC :ITEMS JSON))
    (REMOVE-IF (LAMBDA (E) (EQUAL (CAR E) :IGNORE)) (CDR (ASSOC :ITEMS JSON))))

这是错误的(E是一个((:KEY . "value1") (:IGNORE 1 2 3))形式的列表,但我看不出如何正确使用我的代码。

NB2:这就是我在Clojure中的表现:

(update json :items #(map (fn [x] (dissoc x :ignore)) %))

3 个答案:

答案 0 :(得分:4)

其他答案已经显示了使用alists执行此操作的方法,但您也可以自定义json解码器以返回更方便的数据结构。由于您希望在功能上/持久性地使用它们,FSet库可能是一个不错的选择。

(ql:quickload '(cl-json :fset :alexandria))
(use-package :alexandria)

首先,让我们自定义解码器,将json对象转换为FSET:MAP,将json数组转换为FSET:SEQ

(defvar *current-array* nil)
(defun beginning-of-array ()
  (setf *current-array* (fset:empty-seq)))
(defun array-member (item)
  (fset:push-last *current-array* item))
(defun end-of-array ()
  *current-array*)

(defvar *current-object* nil)
(defvar *current-object-key* nil)
(defun beginning-of-object ()
  (setf *current-object* (fset:empty-map)))
(defun object-key (key)
  (setf *current-object-key*
        (funcall json:*identifier-name-to-key*
                 (funcall json:*json-identifier-name-to-lisp* key))))
(defun object-value (value)
  (fset:includef *current-object* *current-object-key* value))
(defun end-of-object ()
  *current-object*)

(defun call-with-fset-decoder-semantics (function)
  (let ((json:*aggregate-scope-variables*
          (list* '*current-array* '*current-object* '*current-object-key*
                 json:*aggregate-scope-variables*)))
    (json:bind-custom-vars (:beginning-of-array #'beginning-of-array
                            :array-member #'array-member
                            :end-of-array #'end-of-array
                            :beginning-of-object #'beginning-of-object
                            :object-key #'object-key
                            :object-value #'object-value
                            :end-of-object #'end-of-object)
      (funcall function))))

(defmacro with-fset-decoder-semantics (&body body)
  `(call-with-fset-decoder-semantics (lambda () ,@body)))

假设您的JSON看起来像这样。

(defvar *json* "{
                  \"something\": \"foo\",
                  \"items\": [
                    {
                      \"key\": \"value1\", 
                      \"ignore\": [1, 2, 3]
                    },
                    {
                      \"key\": \"value2\", 
                      \"ignore\": [1, 2, 3]
                    }
                  ],
                  \"other\": \"bar\"
                }")

现在您可以将其解码为FSet结构。请注意,FSet使用其自定义阅读器语法来打印对象。 #{| (key value)* |}表示FSET:MAP#[ ... ]表示FSET:SEQ。如果您希望能够阅读它们,可以使用FSET:FSET-SETUP-READTABLE,但这不是必需的。您应该在执行此操作之前创建自定义可读表。

(with-fset-decoder-semantics
  (json:decode-json-from-string *json*))
;=> #{|
;      (:ITEMS
;       #[
;         #{| (:KEY "value1") (:IGNORE #[ 1 2 3 ]) |}
;         #{| (:KEY "value2") (:IGNORE #[ 1 2 3 ]) |} ])
;      (:OTHER "bar")
;      (:SOMETHING "foo") |}

您可以使用FSET:IMAGE:IGNORE的项中删除FSET:LESS项。 FSET:WITH会为地图添加一个键,或替换现有键。

(let ((data (with-fset-decoder-semantics
              (json:decode-json-from-string *json*))))
  (fset:with data :items (fset:image (rcurry #'fset:less :ignore)
                                     (fset:lookup data :items))))
;=> #{|
;      (:ITEMS #[ #{| (:KEY "value1") |} #{| (:KEY "value2") |} ])
;      (:OTHER "bar")
;      (:SOMETHING "foo") |}

FSet还可以方便地处理修改宏:

(let ((data (with-fset-decoder-semantics
              (json:decode-json-from-string *json*))))
  (fset:imagef (fset:lookup data :items) (rcurry #'fset:less :ignore))
  data)
;=> #{|
;      (:ITEMS #[ #{| (:KEY "value1") |} #{| (:KEY "value2") |} ])
;      (:OTHER "bar")
;      (:SOMETHING "foo") |}

这可能看起来好像是破坏性地修改数据,但实际上并非如此。

(let* ((data (with-fset-decoder-semantics
               (json:decode-json-from-string *json*)))
       (old-data data))
  (fset:imagef (fset:lookup data :items) (rcurry #'fset:less :ignore))
  (values data old-data))
;=> #{|
;      (:ITEMS #[ #{| (:KEY "value1") |} #{| (:KEY "value2") |} ])
;      (:OTHER "bar")
;      (:SOMETHING "foo") |}
;=> #{|
;      (:ITEMS
;       #[
;         #{| (:KEY "value1") (:IGNORE #[ 1 2 3 ]) |}
;         #{| (:KEY "value2") (:IGNORE #[ 1 2 3 ]) |} ])
;      (:OTHER "bar")
;      (:SOMETHING "foo") |}

答案 1 :(得分:2)

快速拍摄,以下是Clojure update端口的不完整尝试:

(defun update (a-list keyword fun)
  "Creates a new a-list from the given a-list where the item under the given
keyword is replaced by the value created by applying fun to it.  Expects the
a-list to have only keywords as keys and those keywords to be unique."
  (loop :for el :in a-list
        :for (a . d) := el
        :if (eq a keyword)
          :collect (cons a (funcall fun d))
        :else
          :collect el))

在您的情况下,它可以像这样使用:

(update '((:ITEMS ((:KEY . "value1") (:IGNORE 1 2 3))
                  ((:KEY . "value2") (:IGNORE 1 2 3))))
        :items
        (lambda (items)
          (mapcar (lambda (item)
                    (remove :ignore item :key #'car))
                  items)))

返回:

((:ITEMS ((:KEY . "value1")) ((:KEY . "value2"))))

(这应该稍微改进一下,例如,一旦找到密钥就可以快捷方式,允许其他密钥,参数化常用密钥和测试功能等。)

答案 2 :(得分:2)

执行此操作的基本非破坏性方法是:

(let ((data '((:ITEMS
               ((:KEY . "value1") (:IGNORE 1 2 3))
               ((:KEY . "value2") (:IGNORE 1 2 3))))))
  (acons :items
         (mapcar (lambda (alist)
                   (remove :ignore alist :key #'car))
                 (cdr (assoc :items data)))
         (remove :items data :key #'car)))

=> ((:ITEMS ((:KEY . "value1")) ((:KEY . "value2"))))

最后一次拨打"删除"并非绝对必要;如果你离开"数据"相反,您将在结果列表中保留先前:items的副本。是否要这样做取决于您的需求。

Clojurify所有的东西

(defun arem (key &optional (alist nil alistp))
  (if alistp
      #1=(remove key alist :key #'car)
      (lambda (alist) #1#)))

(defun update-fn (fn key &optional (alist nil alistp))
  (if alistp
      #1=(acons key (funcall fn (cdr (assoc key alist))) (arem key alist))
      (lambda (alist) #1#)))

现在看起来像这样:

(update-fn (lambda (u) (mapcar (arem :ignore) u))
           :items
           '((:ITEMS
              ((:KEY . "value1") (:IGNORE 1 2 3))
              ((:KEY . "value2") (:IGNORE 1 2 3)))))

最后:

(defmacro each (expr &aux (list (gensym)))
  `(lambda (,list)
     (mapcar ,expr ,list)))

给出了:

(update-fn (each (arem :ignore))
           :items
           '((:ITEMS
              ((:KEY . "value1") (:IGNORE 1 2 3))
              ((:KEY . "value2") (:IGNORE 1 2 3)))))

=> ((:ITEMS ((:KEY . "value1")) ((:KEY . "value2"))))

你也可以写:

(update-fn (each (arem :ignore)) :items)

......你获得了一个封闭。