Skip to content

Instantly share code, notes, and snippets.

@dvingo
Last active May 9, 2022 09:45
Show Gist options
  • Save dvingo/44a040ba9d0de2284e151811945a0cd0 to your computer and use it in GitHub Desktop.
Save dvingo/44a040ba9d0de2284e151811945a0cd0 to your computer and use it in GitHub Desktop.
Clojure zipper for recursively traversing a map
;; inspired by: https://clojuredocs.org/clojure.zip/zipper#example-54d91161e4b081e022073c72
(defn map-zipper
[m ref-keys]
{:pre [(set? ref-keys)]}
(z/zipper
(fn is-branch? [x]
(let [ret
(cond
(not (coll? x))
false
(map-entry? x)
(and (contains? ref-keys (key x)) (coll? (val x)))
:else (or (map? x) (set? x)))]
ret))
;; Get the children, given a branch node.
;; we know the possible types that are passed here: truthy return values from `is-branch?`
(fn get-children [x]
(let [ret
(seq
(if (or (map? x) (set? x))
x
(nth x 1)))]
ret))
;; Given an existing node and a seq of children, return new branch node
(fn make-node [x children]
(cond
(or (map? x) (set? x))
(into (empty x) children)
:else
(let [v (val x)
ret (assoc x 1 (into (empty v) children))]
ret)))
m))
;; Inspired by https://tbaldridge.pivotshare.com/media/zippers-episode-1/11348/feature?t=0
;; One recursive version, one loop version.
(defn zip-map
[f zip]
(if (z/end? zip)
(z/root zip)
(if (z/branch? zip)
(recur f (z/next zip))
(recur f (-> zip (z/edit f) z/next)))))
(defn zip-map2 [f zip]
(loop [zip zip]
(if (z/end? zip)
(z/root zip)
(recur
(if (z/branch? zip)
(z/next zip)
(-> zip (z/edit f) z/next))))))
(def tasks
[{:crux.db/id :task-1
:children #{:task-2 :task-3 :task-4}
:name "task 1"}
{:crux.db/id :task-2 :children #{:task-5} :name "task 2"}
{:crux.db/id :task-3 :children #{} :name "task 3"}
{:crux.db/id :task-4 :children #{} :name "task 4"}
{:crux.db/id :task-5 :children #{} :name "task 5"}])
(defn easy-ingest
"Uses crux put transaction to add a vector of documents to a specified node."
[node docs]
(crux/submit-tx node
(vec (for [doc docs]
[:crux.tx/put doc]))))
(easy-ingest node tasks)
(def task-1 (crux/entity (crux/db crux-node) :task-1))
(def myz (map-zipper task-1 #{:children}))
(zip-map
(fn [item]
(println "mapping item: " item)
(if (keyword? item) (crux/entity (crux/db crux-node) item)
item))
myz))
;; Returns =>
{:crux.db/id :task-1,
:children #{{:crux.db/id :task-4, :children #{}, :name "task 4"}
{:crux.db/id :task-2, :children #{{:crux.db/id :task-5, :children #{}, :name "task 5"}}, :name "task 2"}
{:crux.db/id :task-3, :children #{}, :name "task 3"}},
:name "task 1"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment