使用Clojure中的Specter移除嵌套值

4
假设我有一个Clojure的map,像这样:

(def mymap {:a [1 2 3] :b {:c [] :d [1 2 3]}})

我希望有一个函数 remove-empties,可以产生一个新的地图,其中来自(:b mymap) 的值为空序列的条目被删除。因此,(remove-empties mymap) 将给出以下值:

{:a [1 2 3] :b {:d [1 2 3]}}

有没有一种使用Specter编写函数完成这个任务的方法?
7个回答

6
以下是使用Specter进行操作的步骤:

以下是使用Specter的方法:

(use 'com.rpl.specter)

(setval [:b MAP-VALS empty?] NONE my-map)
=> {:a [1 2 3], :b {:d [1 2 3]}}

在中文中,这段话的意思是:“在:b下面,找到所有的映射值为empty?的内容。将它们设置为NONE,也就是将它们移除。”

3
(update my-map :b (fn [b]
                    (apply dissoc b 
                           (map key (filter (comp empty? val) b)))))

1
这是幽灵解决方案:
(ns myns.core
  (:require
   [com.rpl.specter :as spc]))

(def my-map
  {:a [1 2 3]
   :b {:c []
       :d [1 2 3]}})

(defn my-function
  [path data]
  (let [pred #(and (vector? %) (empty? %))]
    (spc/setval [path spc/MAP-VALS pred] spc/NONE data)))

;; (my-function [:b] my-map) => {:a [1 2 3]
;;                               :b {:d [1 2 3]}}

Specter的强大之处在于与基本解决方案的比较,这是一个很好的例证。 - Inqwell

0

到目前为止,我还没有找到使用Specter的filterer的方法,因为当我测试过滤器时,它们似乎会两次接收每个映射条目(一次作为映射条目,一次作为2长度向量),并且在这些之间给出不同的结果似乎会导致问题。然而,我们不应该在任何可能出现空序列的地方删除它们,只应该删除值为它们的映射条目。

不过,我似乎已经成功使用了clojure.walk的方法,这可能仍然会引起您的兴趣。

(ns nested-remove
  (:require [com.rpl.specter :as s]          
            [clojure.walk :refer [postwalk]]))


(defn empty-seq-entry? [entry]
  (and (map-entry? entry) (sequential? (val entry)) (empty? (val entry))))

(defn remove-empties [root]
  (postwalk #(if (map? %) (into (empty %) (remove empty-seq-entry? %)) %) root))

(remove-empties mymap) ;;=> {:a [1 2 3], :b {:d [1 2 3]}}

0

我也不了解Specter,但在普通的Clojure中实现这个功能非常简单。

(defn remove-empties [m]
  (reduce-kv (fn [acc k v]
               (cond (map? v) (let [new-v (remove-empties v)]
                                (if (seq new-v)
                                  (assoc acc k new-v)
                                  acc))
                     (empty? v) acc
                     :else (assoc acc k v)))
             (empty m), m))

注意:对于极度嵌套的数据结构,可能会导致堆栈溢出。

0
假设我们只需要进入一层,而不像被接受的答案那样进行递归搜索:
(setval [:b MAP-VALS empty?] NONE mymap)

一个完全递归的解决方案,可以在任何层级上删除映射中的空值。
(def my-complex-map {:a [1] :b {:c [] :d [1 2 3] :e {:f "foo" :g []}}})

; declare recursive path that traverses map values
(declarepath DEEP-MAP-VALS)
(providepath DEEP-MAP-VALS (if-path map? [MAP-VALS DEEP-MAP-VALS] STAY))

(setval [DEEP-MAP-VALS empty?] NONE my-complex-map)
; => {:a [1], :b {:d [1 2 3, :e {:f "foo"}}}}

请参考维基页面 递归使用Specter


-1

虽然我对Specter不是很熟悉,但除了使用postwalk解决方案外,您还可以使用tupelo.forest从Tupelo库解决此问题。 您需要将数据稍微重新排列成Hiccup或Enlive格式,然后就可以轻松地识别任何没有子节点的节点:

(ns tst.clj.core
  (:use clj.core tupelo.test)
  (:require
    [tupelo.core :as t]
    [tupelo.forest :as tf] ))
(t/refer-tupelo)

(defn hid->enlive [hid]
  (tf/hiccup->enlive (tf/hid->hiccup hid)))

(defn empty-kids?
  [path]
  (let [hid     (last path)
        result  (and (tf/node-hid? hid)
                  (empty? (grab :kids (tf/hid->tree hid))))]
       result))

; delete any nodes without children
(dotest
  (tf/with-forest (tf/new-forest)
    (let [e0          {:tag     :root
                       :attrs   {}
                       :content [{:tag     :a
                                  :attrs   {}
                                  :content [1 2 3]}
                                 {:tag     :b
                                  :attrs   {}
                                  :content [{:tag     :c
                                             :attrs   {}
                                             :content []}
                                            {:tag     :d
                                             :attrs   {}
                                             :content [1 2 3]}
                                            ]}]}
          root-hid    (tf/add-tree-enlive e0)
          empty-paths (tf/find-paths-with root-hid [:** :*] empty-kids?)
          empty-hids  (mapv last empty-paths)]
       (is= (hid->enlive root-hid) ; This is the original tree structure (Enlive format)
         {:tag   :root,
          :attrs {},
          :content
                 [{:tag   :a,
                   :attrs {},
                   :content
                          [{:tag :tupelo.forest/raw, :attrs {}, :content [1]}
                           {:tag :tupelo.forest/raw, :attrs {}, :content [2]}
                           {:tag :tupelo.forest/raw, :attrs {}, :content [3]}]}
                  {:tag   :b,
                   :attrs {},
                   :content
                          [{:tag :c, :attrs {}, :content []}
                           {:tag   :d,
                            :attrs {},
                            :content
                                   [{:tag :tupelo.forest/raw, :attrs {}, :content [1]}
                                    {:tag :tupelo.forest/raw, :attrs {}, :content [2]}
                                    {:tag :tupelo.forest/raw, :attrs {}, :content [3]}]}]}]})
      (apply tf/remove-hid empty-hids) ; remove the nodes with no child nodes
      (is= (hid->enlive root-hid) ; this is the result (Enlive format)
        {:tag   :root,
         :attrs {},
         :content
                [{:tag   :a,
                  :attrs {},
                  :content
                         [{:tag :tupelo.forest/raw, :attrs {}, :content [1]}
                          {:tag :tupelo.forest/raw, :attrs {}, :content [2]}
                          {:tag :tupelo.forest/raw, :attrs {}, :content [3]}]}
                 {:tag   :b,
                  :attrs {},
                  :content
                         [{:tag   :d,
                           :attrs {},
                           :content
                                  [{:tag :tupelo.forest/raw, :attrs {}, :content [1]}
                                   {:tag :tupelo.forest/raw, :attrs {}, :content [2]}
                                   {:tag :tupelo.forest/raw, :attrs {}, :content [3]}]}]}]})
      (is= (tf/hid->hiccup root-hid) ; same result in Hiccup format
        [:root
         [:a
          [:tupelo.forest/raw 1]
          [:tupelo.forest/raw 2]
          [:tupelo.forest/raw 3]]
         [:b
          [:d
           [:tupelo.forest/raw 1]
           [:tupelo.forest/raw 2]
           [:tupelo.forest/raw 3]]]])
  )))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接