Last active
May 9, 2022 09:45
-
-
Save dvingo/44a040ba9d0de2284e151811945a0cd0 to your computer and use it in GitHub Desktop.
Clojure zipper for recursively traversing a map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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