How to Add a New Node to Hash-Map Graph in Clojure? - vector

I have the following array-map created in Clojure.
{:node 7, :children [{:node 8, :children []} {:node 6, :children []} {:node 23, :children {}} {:node 43, :children []}]}
How do i go about adding elements into this,
running the following code
(def tree (assoc-in tree [:node] 12))
gives me
{:node 12, :children [{:node 8, :children []} {:node 6, :children []} {:node 10, :children {}} {:node 13, :children []} {:node 28, :children []}]}`
and running
(def tree (assoc-in tree [:node :children] 12))
gives me the following error message. how do i add elements into the children sections on the array-map
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to clojure.lang.Associative,

Let's assign our tree to t:
(def t {:node 7,
:children [{:node 8, :children []}
{:node 6, :children []}
{:node 23, :children []}
{:node 43, :children []}]})
To add a new child note:
(defn add-child [tree node]
(assoc-in tree
[:children]
(conj (:children tree) node)))
(add-child t :foo)
;; => {:node 7,
;; :children [{:node 8, :children []}
;; {:node 6, :children []}
;; {:node 23, :children []}
;; {:node 43, :children []}
;; :foo]}
Of course this is not exactly what we want.
(defn make-node [value children]
(let [c (into [] children)]
{:node value
:children c}))
(make-node 5 nil)
;; => {:node 5, :children []}
(make-node 5 [(make-node 3 nil) (make-node 7 nil)])
;; => {:node 5,
;; :children [{:node 3, :children []}
;; {:node 7, :children []}]}
Building trees is now a matter of combining make-node & add-child.
If you want to work on deep hierarchies, I suggest using a zipper.

for adding a new node, the basic idea is general. anyway, the prog needs to know where to place the new child. when arriving the parent node of the new child, the prog appends the new child to it. the rest problem is how to traverse a graph in clojure. clojure.walk module is for that purpose.
one implementation of adding a new node to your graph is as follows:
(defn tree-add
[root parent-key new-node]
(clojure.walk/postwalk #(if (= parent-key (:node %))
(assoc % :children (conj (:children %) new-node))
%)
root))
testing:
user> a
{:node 7, :children [{:node 8, :children []}]}
user> (tree-add a 7 {:node 99 :children []})
{:node 7, :children [{:node 8, :children []} {:node 99, :children []}]}
user> (tree-add a 8 {:node 199 :children []})
{:node 7, :children [{:node 8, :children [{:node 199, :children []}]}]}

Related

How can I turn an ordered tree into a collection of named nodes in Clojure?

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}]

Filtering unnecessary keys in huge clojure map

I got a really big ass nested map in Clojure and I am searching for the most idiomatic way to kick out keys, which should not provided to the frontend (yes, this clojure service is running in the backend).
The datastructure looks like:
(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.
BUT...is there some way of describing the data I want to get so that every keys I do not want, get filtered out?
Thx
yet another way, without using any external libs, employing 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}]}]}
The pre/postwalk is there for the exact use case you have: walk down the heterogenous collections, transforming values if necessary
If you want to filter for the top-level keys only you can use select-keys
If you want to remove deeply nested keys you can use specter. For example to remove all values under :h under :g under every items in the vector under :d just write:
user> (setval [:d ALL :g :h] NONE data)
The simplest is to use clojure.walk/postwalk. I'm assuming you don't need to worry about any key combinations like "remove :i only if it's a child of :f".
Here is an example:
(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))
This shows the recursive processing of postwalk, with output:
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}}]}]}
You can see that maps are first turned into vectors of key-value pairs like [:n 17]. So, when you get a 2-vec like that, just look at the first item and return a nil if you don't like it:
(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))
and output:
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.
Don't forget the Clojure CheatSheet.
Here is the doc for spyx.
This might be using more "manual lifting" than required, but a simple recursive function handles this well:
(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}}]}
Once you take into consideration the explanatory comments, it isn't as big as it looks. f (named rec internally) is a recursive function that dissocs keys from the found map when they're in the supplied list of keys. When the value it finds is a map or vector, it recurses to search them as well.
Instead of using blacklist, we wanted to have some kind of whitelist. In production it is not a very good idea to work with blacklist - if for some reason the response object may be get extended. Therefore we now use https://github.com/metosin/spec-tools with the strip-extra-keys-transformer like:
(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)

Add key with default value to nested clojure hashmap

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"}

How to return all keywords in a map

I want to return all the keywords appeared in map. For instance:
{:a 1 :d 4 :e 4}
I want to get (:a :d :e). my code is
(get {:a 1 :d 4 :e 4} :keywords)
It returns nil. So how to fix it?
I guess you want to get all the keys from your map. You can do that using:
(keys {:a 1, :d 4, :e 4}
;; => (:a :d :e)
If you would like to get all the keywords when they appear as keys in a map you need to filter only those matching keyword? predicate:
(filter keyword? (keys {:a 1, 'd 4, :e 4, "f" 5}))
;; => (:a :e)
Similarly for keywords from map values:
(filter keyword? (vals {:a :b, "c" :d, 4 "e", 5 'f}))
;; => (:b :d)

Is there Clojure function that swaps two keys values in persistent map?

Is there Clojure function that swaps values of two keys in persistent map?
I mean something like this:
(defn swap-keys [map k1 k2]
(let [f (get map k1) s (get map k2)]
(assoc map k1 s k2 f)))
(swap-keys {:a 1 :b 2 :c 3 :d 4} :a :c)
;; => {:a 3, :b 2, :c 1, :d 4}
The best I know of is this:
(clojure.set/rename-keys {:a 1 :b 2 :c 3 :d 4}
{:a :c, :c :a})
;; {:c 1, :b 2, :a 3, :d 4}
You can use it to do more than just a two-way swap, if you want:
(clojure.set/rename-keys {:a 1 :b 2 :c 3 :d 4}
{:a :b,
:b :c,
:c :d,
:d: :a})
;; {:b 1, :c 2, :d 3, :a 4}
Edit: A benefit (probably) to this approach over the "naive" solution is that it performs a check to make sure the necessary keys actually exist:
=> (defn swap [m k1 k2] (assoc m k1 (m k2) k2 (m k1)))
=> (swap {:a 1 :b 2 :c 3} :a :d)
;; {:a nil, :b 2, :c 3, :d 1}
=> (clojure.set/rename-keys {:a 1 :b 2 :c 3} {:a :d, :d :a})
;; {:b 2, :c 3, :d 1}

Resources