Filtern unnötiger Schlüssel in einer großen Clojure-Karte

Ich habe eine wirklich große, verschachtelte Map in Clojure und suche nach der idiomatischsten Methode, um Keys rauszuwerfen, die dem Frontend nicht zur Verfügung gestellt werden sollten (ja, dieser Clojure-Dienst läuft im Backend).

Die Datenstruktur sieht folgendermaßen aus:

(def data
  {:a 1
   :b 2
   :c 3
   :d [{:e 5}
       {:f 6
        :g {
            :h 8
            :i 9
            :j 10}
        :l [{
             :m 11
             :n 12
             :p {:q 13
                 :r 14
                 :s 15
                 }}
            {:m 16
             :n 17
             :p {:q 18
                 :r 19
                 :s 20
                 }}]}]})

As you can see, I got a map with keys, whereby some keys got lists with maps, which have some lists again...so I know -> not pretty.

ABER ... gibt es eine Möglichkeit, die Daten zu beschreiben, die ich erhalten möchte, damit alle Schlüssel, die ich nicht möchte, herausgefiltert werden?

Vielen Dank

2
Was möchten Sie aus der Eingabe extrahieren? Sie haben eine große alte Karte eingefügt und gesagt, Sie wollen nur etwas davon, aber welchen Teil? Wie entscheiden Sie, was es wert ist, aufgenommen zu werden?
hinzugefügt der Autor amalloy, Quelle
Vielleicht, aber ich bin das der beste Weg? Ich könnte es auch iterativ machen ... aber vielleicht kann ein Schema geschrieben werden, das ich irgendwie auf die Daten anwenden kann.
hinzugefügt der Autor Tobias K., Quelle

5 Antworten

Auf andere Weise, ohne externe Bibliotheken zu verwenden, verwenden Sie clojure.walk :

(defn remove-deep [key-set data]
  (clojure.walk/prewalk (fn [node] (if (map? node)
                                     (apply dissoc node key-set)
                                     node))
                        data))


user> (remove-deep [:i :l] data)
;;=> {:a 1, :b 2, :c 3, :d [{:e 5} {:f 6, :g {:h 8, :j 10}}]}

user> (remove-deep [:f :p] data)
;;=> {:a 1, :b 2, :c 3, :d [{:e 5} {:g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12} {:m 16, :n 17}]}]}

Der Pre/Postwalk ist für den genauen Anwendungsfall da, den Sie haben: Gehen Sie die heterogenen Sammlungen ab und transformieren Sie gegebenenfalls Werte

7
hinzugefügt
Ja, das ist einfacher als die Suche nach 2-Vecs (MapEntry-Paaren).
hinzugefügt der Autor Alan Thompson, Quelle
@slipset, tree-seq eignet sich gut für die Durchquerung, ist jedoch für die -Modifikation unbrauchbar
hinzugefügt der Autor leetwinski, Quelle

Das einfachste ist, clojure.walk/postwalk zu verwenden. Ich gehe davon aus, dass Sie sich keine Gedanken über Tastenkombinationen wie "remove: i" machen müssen, nur wenn es ein Kind von: f ist.

Hier ist ein Beispiel:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require [clojure.walk :as walk]))

(def data
  {:a 1
   :b 2
   :c 3
   :d [{:e 5}
       {:f 6
        :g {
            :h 8
            :i 9
            :j 10}
        :l [{
             :m 11
             :n 12
             :p {:q 13
                 :r 14
                 :s 15
                 }}
            {:m 16
             :n 17
             :p {:q 18
                 :r 19
                 :s 20
                 }}]}]})

(defn remove-keys [data keys]
  (let [proc-node  (fn [node]
                     (spyx node))
        result (walk/postwalk proc-node data) ]
    (spyx-pretty result)))

(def bad-keys #{:b :f :i :p :n})

(dotest
  (remove-keys data bad-keys))

Dies zeigt die rekursive Verarbeitung von postwalk mit der Ausgabe:

Testing tst.demo.core
node => :a
node => 1
node => [:a 1]
node => :b
node => 2
node => [:b 2]
node => :c
node => 3
node => [:c 3]
node => :d
node => :e
node => 5
node => [:e 5]
node => {:e 5}
node => :f
node => 6
node => [:f 6]
node => :g
node => :h
node => 8
node => [:h 8]
node => :i
node => 9
node => [:i 9]
node => :j
node => 10
node => [:j 10]
node => {:h 8, :i 9, :j 10}
node => [:g {:h 8, :i 9, :j 10}]
node => :l
node => :m
node => 11
node => [:m 11]
node => :n
node => 12
node => [:n 12]
node => :p
node => :q
node => 13
node => [:q 13]
node => :r
node => 14
node => [:r 14]
node => :s
node => 15
node => [:s 15]
node => {:q 13, :r 14, :s 15}
node => [:p {:q 13, :r 14, :s 15}]
node => {:m 11, :n 12, :p {:q 13, :r 14, :s 15}}
node => :m
node => 16
node => [:m 16]
node => :n
node => 17
node => [:n 17]
node => :p
node => :q
node => 18
node => [:q 18]
node => :r
node => 19
node => [:r 19]
node => :s
node => 20
node => [:s 20]
node => {:q 18, :r 19, :s 20}
node => [:p {:q 18, :r 19, :s 20}]
node => {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}
node => [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]
node => [:l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]]
node => {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}
node => [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]
node => [:d [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]]
node => {:a 1, :b 2, :c 3, :d [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]}
result => 
{:a 1,
 :b 2,
 :c 3,
 :d
 [{:e 5}
  {:f 6,
   :g {:h 8, :i 9, :j 10},
   :l
   [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}}
    {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]}

Sie können sehen, dass Karten zuerst in Vektoren von Schlüsselwertpaaren umgewandelt werden, z. B. [: n 17] . Wenn Sie also einen solchen 2-Vec-Code erhalten, schauen Sie sich einfach den ersten Eintrag an und geben Sie einen nil zurück, wenn Sie ihn nicht mögen:

(defn len-2-vec? [node]
  (and (sequential? node)
    (= 2 (count node))))

(defn remove-keys [data bad-keys]
  (let [proc-node (fn [node]
                    (if (and (len-2-vec? node)
                          (contains? bad-keys (first node)))
                      (do
                        (spyx :removed node)
                        nil)
                      node))
        result (walk/postwalk proc-node data) ]
    (spyx-pretty result)))

(def bad-keys #{:b :f :i :p :n})

(dotest
  (remove-keys data bad-keys))

und ausgabe:

Testing tst.demo.core
:removed    node => [:b 2]
:removed    node => [:f 6]
:removed    node => [:i 9]
:removed    node => [:n 12]
:removed    node => [:p {:q 13, :r 14, :s 15}]
:removed    node => [:n 17]
:removed    node => [:p {:q 18, :r 19, :s 20}]

(remove-keys data bad-keys) => 
{:a 1, 
 :c 3, 
 :d [{:e 5} 
     {:g {:h 8, 
          :j 10}, 
      :l [{:m 11}
          {:m 16}]}]}

Ran 2 tests containing 0 assertions.
0 failures, 0 errors.

Vergessen Sie nicht das Clojure CheatSheet .

Here is the doc for spyx.

4
hinzugefügt

If you want to filter for the top-level keys only you can use select-keys

Wenn Sie tief verschachtelte Schlüssel entfernen möchten, können Sie den spectre verwenden. Um beispielsweise alle Werte unter : h unter : g unter allen Elementen im Vektor unter : d zu entfernen, schreiben Sie einfach:

user> (setval [:d ALL :g :h] NONE data)
4
hinzugefügt

Möglicherweise verwendet man mehr "manuelles Anheben" als erforderlich, aber eine einfache rekursive Funktion kann dies gut bewältigen:

(defn filter-nested [root keys-to-remove]
  (let [should-remove? (set keys-to-remove)

        ; A recursive function to search through the map
        f (fn rec [node]
            (reduce-kv (fn [acc k v]
                         (cond
                           ; If it's in the set, remove the key from the node
                           (should-remove? k) (dissoc acc k)

                           ; If the value is a map, recursively search it too
                           (map? v) (assoc acc k (rec v))

                           ; If it's a vector, map a recursive call over the vector
                           (vector? v) (assoc acc k (mapv rec v))

                           ; Else do nothing
                           :else acc))
                       node
                       node))]
    (f root)))

(filter-nested data #{:l})
=> {:a 1, :b 2, :c 3, :d [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}}]}

Wenn Sie die erklärenden Kommentare in Betracht ziehen, ist es nicht so groß, wie es aussieht. f (intern mit rec bezeichnet) ist eine rekursive Funktion, die dissoc von der gefundenen Karte entfernt, wenn sie sich in der bereitgestellten Schlüsselliste befinden. Wenn es sich bei dem gefundenen Wert um eine Karte oder einen Vektor handelt, rekursiert es auch, um sie zu durchsuchen.

3
hinzugefügt

Anstelle der Blacklist wollten wir eine Art Whitelist haben. In der Produktion ist es keine gute Idee, mit der Blacklist zu arbeiten - falls das Antwortobjekt aus irgendeinem Grund erweitert werden kann. Deshalb verwenden wir jetzt https://github.com/metosin/spec-tools mit dem < code> streifen-extra-schlüssel-transformator wie:

(ns sexy.helper.transformer
  (:require [spec-tools.core :as st]
            [spec-tools.data-spec :as ds]))

(def my-abc {:a "12345"
               :b "00529"
               :c [{:d "Kartoffel"
                    :e 5}
                   {:d "Second Item"
                    :e 9999}]})

(def the-abc
  {:a string?
   :c [{:d string?}]})

(def abc-spec
  (ds/spec ::abc the-abc))

(st/conform abc-spec my-abc st/strip-extra-keys-transformer)
1
hinzugefügt