2015-11-08 14 views
5

Sto cercando una funzione simile a quelli in clojure.walk che hanno una funzione inner che prende come argomento:Clojure - camminare con percorso

  • non una chiave e un valore, come è il caso con il clojure. walk/walk function
  • ma il vettore di chiavi necessarie per accedere a un valore dalla struttura di dati di livello superiore.
  • attraversa ricorsivamente tutti i dati

Esempio:

;; not good since it takes `[k v]` as argument instead of `[path v]`, and is not recursive. 
user=> (clojure.walk/walk (fn [[k v]] [k (* 10 v)]) identity {:a 1 :b {:c 2}}) 
;; {:a 10, :c 30, :b 20} 

;; it should receive as arguments instead : 
[[:a] 1] 
[[:b :c] 2] 

Nota:

  • dovrebbe funzionare con array anche utilizzando i tasti 0, 1, 2 ... (proprio come in get-in).
  • Non mi interessa davvero il parametro outer, se questo consente di semplificare il codice.

risposta

7

Attualmente clojure di apprendimento, ho provato questo come esercizio. Tuttavia, ho trovato abbastanza complicato implementarlo direttamente come una passeggiata lungo l'albero che applica la funzione interiore mentre procede.

Per ottenere il risultato che si sta cercando, ho diviso il compito in 2:

  • Prima trasformare la struttura annidata in un dizionario con il percorso come chiave, e il valore,
  • Poi mappare la funzione interna sopra, o ridurre con la funzione esterna.

mia realizzazione:

;; Helper function to have vector's indexes work like for get-in 
(defn- to-indexed-seqs [coll] 
    (if (map? coll) 
    coll 
    (map vector (range) coll))) 

;; Flattening the tree to a dict of (path, value) pairs that I can map over 
;; user> (flatten-path [] {:a {:k1 1 :k2 2} :b [1 2 3]}) 
;; {[:a :k1] 1, [:a :k2] 2, [:b 0] 1, [:b 1] 2, [:b 2] 3} 
(defn- flatten-path [path step] 
    (if (coll? step) 
    (->> step 
     to-indexed-seqs 
     (map (fn [[k v]] (flatten-path (conj path k) v))) 
     (into {})) 
    [path step])) 

;; Some final glue 
(defn path-walk [f coll] 
    (->> coll 
     (flatten-path []) 
     (map #(apply f %)))) 

;; user> (println (clojure.string/join "\n" (path-walk #(str %1 " - " %2) {:a {:k1 1 :k2 2} :b [1 2 3]}))) 
;; [:a :k1] - 1 
;; [:a :k2] - 2 
;; [:b 0] - 1 
;; [:b 1] - 2 
;; [:b 2] - 3 
+1

Nel tuo esempio, non dovrebbe essere il dato '{[: un: k1] 1, [: un: k2] 2, [: B 0] 1, [ : b 1] 2, [: b 2] 3} '? – nha

+0

Oops, davvero! C'è un errore nel per-index-seguenti del regolamento provvisorio, dovrebbe essere: (vector map (range) Coll) invece di: (vector map Coll (range)) io aggiornare la mia risposta :) – kimsnj

+0

Grazie ! Ho accettato la tua risposta, ma vorrei comunque avere una soluzione che non richieda percorsi di enumerazione (il mio caso d'uso è una migrazione del database che richiede alcune trasformazioni). – nha

0

Si scopre che Stuart Halloway pubblicato a gist che potrebbe essere di qualche utilità (che utilizza un protocollo, che lo rende estendibile pure):

(ns user) 

(def app 
    "Intenal Helper" 
    (fnil conj [])) 

(defprotocol PathSeq 
    (path-seq* [form path] "Helper for path-seq")) 

(extend-protocol PathSeq 
    java.util.List 
    (path-seq* 
    [form path] 
    (->> (map-indexed 
     (fn [idx item] 
      (path-seq* item (app path idx))) 
     form) 
     (mapcat identity))) 

    java.util.Map 
    (path-seq* 
    [form path] 
    (->> (map 
     (fn [[k v]] 
      (path-seq* v (app path k))) 
     form) 
     (mapcat identity))) 

    java.util.Set 
    (path-seq* 
    [form path] 
    (->> (map 
     (fn [v] 
      (path-seq* v (app path v))) 
     form) 
     (mapcat identity))) 


    java.lang.Object 
    (path-seq* [form path] [[form path]]) 

    nil 
    (path-seq* [_ path] [[nil path]])) 

(defn path-seq 
    "Returns a sequence of paths into a form, and the elements found at 
    those paths. Each item in the sequence is a map with :path 
    and :form keys. Paths are built based on collection type: lists 
    by position, maps by key, and sets by value, e.g. 

    (path-seq [:a [:b :c] {:d :e} #{:f}]) 

    ({:path [0], :form :a} 
    {:path [1 0], :form :b} 
    {:path [1 1], :form :c} 
    {:path [2 :d], :form :e} 
    {:path [3 :f], :form :f}) 
    " 
    [form] 
    (map 
    #(let [[form path] %] 
     {:path path :form form}) 
    (path-seq* form nil))) 

(comment 
    (path-seq [:a [:b :c] {:d :e} #{:f}]) 

    ;; finding nils hiding in data structures: 
    (->> (path-seq [:a [:b nil] {:d :e} #{:f}]) 
     (filter (comp nil? :form))) 

    ;; finding a nil hiding in a Datomic transaction 
    (->> (path-seq {:db/id 100 
        :friends [{:firstName "John"} 
          {:firstName nil}]}) 
     (filter (comp nil? :form))) 


) 

Nota: nel mio caso avrei potuto usare anche Specter, quindi se stai leggendo questo, potresti voler controllare anche questo.