Add element to vector in nested tree structure - clojure

Jumping right in, better way of doing this:
(assoc-in
{:children [{:children [{:children [{:children [{:children []}]}]}]}]}
[:children 0 :children 0 :children 0 :children 0 :children 0]
:hello)
I want to insert :hello into deepest :children vector. Above I am doing it with assoc-in.
Is there a better way than assoc-in?
Or, if when assoc-in is the only way, how would you handle assoc-in's 2nd argument [k & ks]?
Also good to know if there it something that also works for inserting :world into and arbitrary :children's vector... like the 3rd child or the 2nd child's 1st child.

The vector argument to assoc-in need not be a literal, so you can construct it as desired.
(def nested-map
{:children [{:children [{:children [{:children [{:children []}]}]}]}]})
(assoc-in nested-map (vec (take 10 (cycle [:children 0]))) :hello)
;=> {:children [{:children [{:children [{:children [{:children [:hello]}]}]}]}]}
Or for 3rd child of the 2nd child of the 1st child, construct a path like
(vec (interleave (repeat :children) [0 1 2]))
;=> [:children 0 :children 1 :children 2]
More generally you can use zippers to arbitrarily move around the nested map, e.g. descend last child. The movement functions are can be composed, etc.
(require '[clojure.zip :as zip])
(def z (zip/zipper map? :children #(assoc % :children (vec %2)) nested-map))
(-> (ffirst (filter (comp zip/end? second) ; find last
(partition 2 1 (iterate zip/next z))))
(zip/edit (constantly :hello)) ; change to :hello
zip/root) ; bubble up changes
;=> {:children [{:children [{:children [{:children [{:children [:hello]}]}]}]}]}

You could also use clojure.walk for this
(require '[clojure.walk :as w])
(def nested-map
{:children [{:children [{:children [{:children [{:children []}]}]}]}]})
(w/postwalk (fn [node] (if (and (vector? node) (empty? node))
(conj node :hello)
node))
nested-map)
=> {:children [{:children [{:children [{:children [{:children [:hello]}]}]}]}]}

Related

Idiomatic way to wrap object into collection (if it's not a collection already) in Clojure?

I have (for instance) a mix of data structures such as {:name "Peter" :children "Mark"} and {:name "Mark" :children ["Julia" "John"] i.e. :children value is either a single string or a collection of strings. Other functions in my code expect that the value of :children is always a collection of strings, so I need to adapt the data for them.
Of course I can use something like:
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children
(if (coll? children)
children
[children]))))
But is there a more idiomatic/laconic way?
I think you will have to take no for an answer.
(if (coll? x) x [x]) is about as terse and expressive as it gets. It’s what people usually use for this problem (sometimes with sequential? instead of coll?).
cond-> enthusiasts like me sometimes try to use it in place of a simple conditional, but here it is no improvement:
(cond-> x (not (coll? x)) vector)
In the context of your code, however, you can do a little better. A lookup and association is best expressed with update:
(defn data-adapter [m]
(update m :children #(if (coll? %) % [%])))
the only advice would be to abstract that logic to some function, to keep your actual business logic clean.
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children (ensure-coll children))))
or, more concise, with update:
(defn data-adapter [m]
(update m :children ensure-coll))
where ensure-coll could be something like this:
(defn iffun [check & {:keys [t f] :or {t identity f identity}}]
#((if (check %) t f) %))
(def ensure-coll (iffun coll? :f list))
(or whatever another implementation you like)
user> (data-adapter {:children 1})
;;=> {:children (1)}
user> (data-adapter {:children [1]})
;;=> {:children [1]}
Perhaps not idiomatic, but laconic:
(flatten [x])
https://clojuredocs.org/clojure.core/flatten

Transforming a map with assoc and dissoc at the same time in clojure

I have a map as seen bellow. I am getting the information from a datomic database. Now I want to transform the data structure here:
(def my-map [{:db/id #object[Object 56536887900242005],
:height 630,
:distance 1474.1,
:coordinates [-26.65622109697031 30.48401767312403],
:location #:location{:id 1}}
{:db/id #object[Object 56536887900242006],
:height 22075,
:distance 1503.2,
:coordinates [-26.65622109697031 30.48401767312403],
:location #:location{:id 2}}
{:db/id #object[Object 56536887900242007],
:height 24248,
:distance 1695.6,
:coordinates [-26.662030943549 30.25648873549992],
:location #:location{:id 3}})
to look like this
{1 {:height 630, :distance 1474.1,:coordinates [-26.65622109697031 30.48401767312403]}
2 {:height 22075, :distance 1503.2,:coordinates [-26.65622109697031 30.48401767312403]}
3 {:height 24248, :distance 1695.6,:coordinates [-26.65622109697031 30.48401767312403]}}
I want to pull the 1 from #:location{:id 1} which I will then assoc with
{:height 22075, :distance 1503.2,:coordinates [-26.65622109697031 30.48401767312403]}
Bellow I have code that return the above, but I do not know how to assoc it into the :id and I also do not know how to get the id seeing that the data has a #
(map #(dissoc % :db/id :location ) my-map)
I use plumbing.core in every project anyway, and if you do as well, then this solution might appeal to you.
(grouped-map
(fn-> :location :location/id)
(fn-> (dissoc :location :db/id))
my-map)
You can write like this:
(into {}
(map
#(hash-map
(get-in % [:location :location/id])
(dissoc % :db/id :location))
my-map))
Sometimes using for can help make the structure of your data more apparent:
(->> (for [record my-map]
[(-> record :location :location/id)
(dissoc record :db/id :location)])
(into {}))
The following function will do the trick:
(defn transform [coll]
(->> coll
(map #(hash-map
(-> % :location :location/id)
(dissoc % :db/id :location)))
(into {})))

Clojure parse nested vectors

I am looking to transform a clojure tree structure into a map with its dependencies
For example, an input like:
[{:value "A"}
[{:value "B"}
[{:value "C"} {:value "D"}]
[{:value "E"} [{:value "F"}]]]]
equivalent to:
:A
:B
:C
:D
:E
:F
output:
{:A [:B :E] :B [:C :D] :C [] :D [] :E [:F] :F}
I have taken a look at tree-seq and zippers but can't figure it out!
Here's a way to build up the desired map while using a zipper to traverse the tree. First let's simplify the input tree to match your output format (maps of :value strings → keywords):
(def tree
[{:value "A"}
[{:value "B"} [{:value "C"} {:value "D"}]
{:value "E"} [{:value "F"}]]])
(def simpler-tree
(clojure.walk/postwalk
#(if (map? %) (keyword (:value %)) %)
tree))
;; [:A [:B [:C :D] :E [:F]]]
Then you can traverse the tree with loop/recur and clojure.zip/next, using two loop bindings: the current position in tree, and the map being built.
(loop [loc (z/vector-zip simpler-tree)
deps {}]
(if (z/end? loc)
deps ;; return map when end is reached
(recur
(z/next loc) ;; advance through tree
(if (z/branch? loc)
;; for (non-root) branches, add top-level key with direct descendants
(if-let [parent (some-> (z/prev loc) z/node)]
(assoc deps parent (filterv keyword? (z/children loc)))
deps)
;; otherwise add top-level key with no direct descendants
(assoc deps (z/node loc) [])))))
=> {:A [:B :E], :B [:C :D], :C [], :D [], :E [:F], :F []}
This is easy to do using the tupelo.forest library. I reformatted your source data to make it fit into the Hiccup syntax:
(dotest
(let [relationhip-data-hiccup [:A
[:B
[:C]
[:D]]
[:E
[:F]]]
expected-result {:A [:B :E]
:B [:C :D]
:C []
:D []
:E [:F]
:F []} ]
(with-debug-hid
(with-forest (new-forest)
(let [root-hid (tf/add-tree-hiccup relationhip-data-hiccup)
result (apply glue (sorted-map)
(forv [hid (all-hids)]
(let [parent-tag (grab :tag (hid->node hid))
kid-tags (forv [kid-hid (hid->kids hid)]
(let [kid-tag (grab :tag (hid->node kid-hid))]
kid-tag))]
{parent-tag kid-tags})))]
(is= (format-paths (find-paths root-hid [:A]))
[[{:tag :A}
[{:tag :B} [{:tag :C}] [{:tag :D}]]
[{:tag :E} [{:tag :F}]]]])
(is= result expected-result ))))))
API docs are here. The project README (in progress) is here. A video from the 2017 Clojure Conj is here.
You can see the above live code in the project repo.

What is the idiomatic way to alter a vector that is stored in an atomized map?

I have an atom called app-state that holds a map. It looks like this:
{:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]}
What is the idiomatic way to remove the element inside the vector with :id = 2 ? The result would look like:
{:skills [{:id 1 :text "hi"}]}
...
So far, I have this:
(defn new-list [id]
(remove #(= id (:id %)) (get #app-state :skills)))
swap! app-state assoc :skills (new-list 2)
It works, but I feel like this isn't quite right. I think it could be something like:
swap! app-state update-in [:skills] remove #(= id (:id %))
But this doesn't seem to work.
Any help is much appreciated!
Try this:
(defn new-list [app-state-map id]
(assoc app-state-map :skills (into [] (remove #(= id (:id %)) (:skills app-state-map)))))
(swap! app-state new-list 2)
swap! will pass the current value of the atom to the function you supply it. There's no need to dereference it yourself in the function.
See the docs on swap! for more details.
(swap! state update :skills (partial remove (comp #{2} :id)))
(def skills {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
(defn remove-skill [id]
(update skills :skills (fn [sks] (vec (remove #(= id (:id %)) sks)))))
You would then be able to call say (remove-skill 1) and see that only the other one (skill with :id of 2) is left.
I like your way better. And this would need to be adapted for use against an atom.
You can use filter to do this. Here is a function that takes an id and the map and let's you filter out anything that doesn't match your criteria. Of course, you can make the #() reader macro check for equality rather than inequality depending on your needs.
user=> (def foo {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
#'user/foo
user=> (defn bar [id sklz] (filter #(not= (:id %) id) (:skills sklz)))
#'user/bar
user=> (bar 1 foo)
({:id 2, :text "yeah"})
user=> (bar 2 foo)
({:id 1, :text "hi"})

Pulling values from complex lists in Clojure

I'm trying to pull the values out of a complicated list structure.
Given something like this:
[{:a "whatever" :b [:c "foo"]} :e {:f "boo"} :g {:h [:i 62281]}]
I'd like to get:
["whatever" "foo" "boo" 62281]
So far I've only gotten to this point:
((62281) nil (boo) nil whatever foo)
Here's the code:
(defn get-values [params]
(apply conj
(map (fn [part]
(if (not (keyword? part))
(map (fn [v]
(if (vector? v)
(last v)
v))
(vals part))))
params)))
I can't seem to get rid of the nil's
I can't figure out why the values after a certain point are in lists.
I figure there's got to be a better way to do this.
Fix the data structure and everything will fall in place. As of now your data structure isn't consistent at all and that will make any function that touch this data way more complicated and error prone. You can model this data as a map:
(def data {:a "whatever"
:b nil
:c "foo"
:e nil
:f "boo"
:g nil
:h nil
:i 62281})
And then to get the desired result:
(->> (vals data)
(filter (comp not nil?))
(into []))
But for some strange reason you still want to parse the data structure you provided then:
(defn get-values [data]
(->> (map #(if (map? %) (into [] %) %) data)
flatten
(filter #(or (string? %) (number? %)))
(into [])))