I think it's best to use an example. Let's say I have an ordered tree:
(def abcd [:a [:b :c] :d])
I want to build from it a collection of key-value maps, each map representing a nodes of this tree, with a random name and all relevant information, that is, its parent (nil for the root node) its index (0, 1, 2 ..) and, if it's a leaf node, its content (like ":a"). For instance, in this case it could be:
[{:name G__36654, :parent nil, :index 0}
{:name G__36655, :content :a, :parent G__36654, :index 0}
{:name G__36656, :parent G__36654, :index 1}
{:name G__36657, :content :b, :parent G__36656, :index 0}
{:name G__36658, :content :c, :parent G__36656, :index 1}
{:name G__36659, :content :d, :parent G__36654, :index 2}]
I defined a function that seems to do what I want, but it uses recursion by calling itself and I'm having trouble figuring out how to use loop-recur instead, and I believe there must be something better out there. Here's my attempt:
(defn mttrav "my tree traversal"
([ptree parent index]
(let [name (gensym)]
(cond
(not (coll? ptree)) [ {:name name :content ptree :parent parent :index index}]
:else (reduce into
[{:name name :parent parent :index index}]
(map-indexed #(mttrav %2 name %1) ptree)))))
([ptree]
(mttrav ptree nil 0)))
BTW, I don't know if a vector is the right collection to use, maybe a set would make more sense, but I'm using a vector for easier debugging, since it's more readable when the order in which nodes are generated is preserved, and if nodes are accidentally repeated I want to see it.
Thanks in advance!
Edit: just to clarify, it would also be acceptable for each node to have a list of :child nodes instead of a :parent node, and some other variations, as long as it's a flat collection of maps, each map representing a node, with a unique :name, and the position, content and parent-child relations of the nodes are captured in this structure. The intended input are hiccup parse trees coming typically from Instaparse, and the maps are meant to become records to insert in a Clara session.
When a tree resists tail recursion, another thing to try is a "zipper" from Clojure's standard library. Zippers shine for editing, but they're also pretty good at linearizing depth-first traversal while keeping structure context available. A typical zipper loop looks like this:
user> (def abcd '(:a (:b :c) :d))
#'user/abcd'
user> (loop [ret [], z (zip/seq-zip abcd)]
(if (zip/end? z)
ret
(let [o {:name 42, :content (zip/node z), :parent 42, :index 42}]
(recur (conj ret o) (zip/next z)))))
[{:name 42, :content (:a (:b :c) :d), :parent 42, :index 42}
{:name 42, :content :a, :parent 42, :index 42}
{:name 42, :content (:b :c), :parent 42, :index 42}
{:name 42, :content :b, :parent 42, :index 42}
{:name 42, :content :c, :parent 42, :index 42}
{:name 42, :content :d, :parent 42, :index 42}]
To fill in :parent and :index, you'll find zipper notation for looking "up" at parents, "left" for siblings, etc., in the official docs at https://clojure.github.io/clojure/clojure.zip-api.html.
I created the zip with seq-zip having modeled nodes as a list. Your specific case models nodes as vectors, which seq-zip does not recognize, so you would presumably use vector-zip or invent your own adapter. You can follow the "Source" link in the docs to see how seq-zip and vector-zip work.
Breadth first traversal is what you need. So if you want to build the list of parents while you traverse the tree, you need to first uniquely identify all your leaf nodes. I'm not sure it can be done without doing that, except if you know for sure that your leafs nodes are unique. It's also getting really late/early here, so my brain is not working optimally. I'm sure my solution can get distilled down a lot.
So if you have a tree like [:a [:b :c] :d [:b :c]], [:b :c] is a parent of :b and :c, but then last two leaf nodes are also :b and :c, so which parent do you choose ?
So let's have a tree whose leaves have unique id.
(defn attach-ids [tree]
(clojure.walk/postwalk (fn [node]
(if (coll? node) node
{:node node :id (gensym)}))
tree))
(def tree (attach-ids [:a [:b :c] :d]))
;; produces this
;; [{:node :a, :id G__21500}
;; [{:node :b, :id G__21501} {:node :c, :id G__21502}]
;; {:node :d, :id G__21503}]
Now for the rest of the solution
(defn add-parent [parent-map id branch]
(assoc parent-map id {:children-ids (set (map :id branch))
:child-nodes (map :node branch)}))
(defn find-parent-id [node parent-map]
(->> parent-map
(filter (fn [[parent-id {children-ids :children-ids}]]
(contains? children-ids (:id node))))
ffirst))
(defn find-index [node parent-map tree]
(if-let [parent-id (find-parent-id node parent-map)]
(let [children (:child-nodes (get parent-map parent-id))]
(.indexOf children (:node node)))
(.indexOf tree node)))
(defn bfs [tree]
(loop [queue tree
parent-map {}
ret []]
(if (not-empty queue)
(let [node (first queue)
rst (vec (rest queue))]
(cond
(map? node)
(recur rst
parent-map
(conj ret (assoc node :parent (find-parent-id node parent-map)
:index (find-index node parent-map tree))))
(vector? node)
(let [parent-id (gensym)]
(recur (into rst node)
(add-parent parent-map parent-id node)
(conj ret {:id parent-id
:index (find-index node parent-map tree)
:parent (find-parent-id node parent-map)})))))
ret)))
(def tree (attach-ids [:a [:b :c] :d]))
(bfs tree)
;; children with :parent nil value point to root
;;[{:node :a, :id G__21504, :parent nil, :index 0}
;; {:id G__21513, :index 1}
;; {:node :d, :id G__21507, :parent nil, :index 2}
;; {:node :b, :id G__21505, :parent G__21513, :index 0}
;; {:node :c, :id G__21506, :parent G__21513, :index 1}]
I tried to do Clojure but stuck with the nested hashmap.
I have a structure like this:
{:type "view"
children: [
{:type "view"
:id "123"}
{:type "view"
:children [
{:type "view"}]}]}
Now I want to add field :id to each hashmap with random string if not exist. To get something like this:
{:type "view"
:id "43434"
children: [
{:type "view"
:id "123"}
{:type "view"
:id "456"
:children [
{:type "view"
:id "5656"}]}]}
You can use clojure.walk/postwalk to do this:
(walk/postwalk
(fn [v]
(if (and (map? v) (nil? (:id v)))
(assoc v :id (str (rand-int 9999)))
v))
data)
=>
{:type "view"
:id "3086"
:children [{:type "view"
:id "123"}
{:type "view"
:id "8243"
:children [{:type "view" :id "3222"}]}]}
...where data is your input map. postwalk is traversing your nested maps, and associating an :id key (a random integer string) on every map that doesn't have one.
in addition to the walk variant there are some more:
the simplest solution would be the recursive update
(defn upd [{:keys [id children] :as data}]
(assoc data
:id (if id
id
(str (rand-int Integer/MAX_VALUE)))
:children (mapv upd children)))
#'user/upd
user> (upd data)
;;=> {:type "view",
;; :children [{:type "view", :id "123", :children []}
;; {:type "view",
;; :children [{:type "view", :id "35223257",
;; :children []}],
;; :id "551012526"}],
;; :id "1899442274"}
you can also use clojure's zippers for that:
(require '[clojure.zip :as z])
(loop [curr (z/zipper map? :children (fn [n ch] (assoc n :children (vec ch))) data)]
(if (z/end? curr)
(z/root curr)
(recur (z/next
(let [node (z/node curr)]
(if (:id node)
curr
(z/replace curr
(assoc node :id
(str "id-" (rand-int Integer/MAX_VALUE))))))))))
;;=> {:type "view",
;; :children [{:type "view", :id "123"}
;; {:type "view",
;; :children [{:type "view", :id "id-92807721"}],
;; :id "id-1357268462"}],
;; :id "id-803083206"}
I have a vector of maps. I want to associate an index element for each element.
Example:
(append-index [{:name "foo"} {:name "bar"} {:name "baz"}])
should return
[{:name "foo" :index 1} {:name "bar" :index 2} {:name "baz" :index 3}]
What is the best way to implement append-index function?
First of all, Clojure starts counting vector elements from 0, so you probably want to get
[{:index 0, :name "foo"} {:index 1, :name "bar"} {:index 2, :name "baz"}]
You could do it pretty easily with map-indexed function
(defn append-index [coll]
(map-indexed #(assoc %2 :index %1) coll))
just adding some fun:
(defn append-index [items]
(map assoc items (repeat :index) (range)))
How to apply a function on every value in a map in vector of maps.
If I have a vector of maps
(def vector-of-maps [{:a 1 :b 2} {:a 3 :b 4}])
And want to apply a function on every value in every map and as a result I want the same vector of maps, something like this
(map #(+ 1 %) vector-of-maps)
So that the result is
[{:a 2 :b 3} {:a 4 :b 5}]
And I want it to work with any vector of maps, not just this particular one....
=> (defn update-map [m f] (reduce-kv (fn [m k v] (assoc m k (f v))) {} m))
=> (map #(update-map % inc) vector-of-maps)
({:b 3, :a 2} {:b 5, :a 4})
Perhaps
(defn mapv-map-values [f vm]
(letfn [(map-values [m] (zipmap (keys m) (map f (vals m))))]
(mapv map-values vm)))
... producing
(mapv-map-values inc [{:a 1 :b 2} {:a 3 :b 4}])
;[{:b 3, :a 2} {:b 5, :a 4}]
The map-values function, the only significant departure from #user100464's answer, was adapted from here.
also you can do like this,but it is ugly
(defn dodo [m] (map (fn [map] (apply merge (for [[k v] map] (assoc {} k (inc v))))) m))
(dodo [{:a 1 :b 2} {:a 3 :b 4}])
;({:a 2, :b 3} {:a 4, :b 5})
Say I have a collection of maps:
(def coll #{{:name "foo"} {:name "bar"}})
I want a function that will add an id (a unique number is fine) to each map element in the collection. i.e.
#{{:id 1 :name "foo"} {:id 2 :name "bar"}}
The following DOES NOT WORK, but it's the line of thinking I currently have.
(defn add-unique-id [coll]
(map assoc :id (iterate inc 0) coll))
Thanks in advance...
If you want to be really, really sure the IDs are unique, use UUIDs.
(defn add-id [coll]
(map #(assoc % :id (str (java.util.UUID/randomUUID))) coll))
How about
(defn add-unique-id [coll]
(map #(assoc %1 :id %2) coll (range (count coll))))
Or
(defn add-unique-id [coll]
(map #(assoc %1 :id %2) coll (iterate inc 0)))