whats the best way to approach normalized trees if i want to be able to do things like get-subtree or get-path-to-root, but still get the output as a normalized tree? as an example:
[{:name :a, :parent :root}
{:name :c, :parent :root}
{:name :d, :parent :c}
{:name :e, :parent :d}
{:name :f, :parent :b}
{:name :b, :parent :a}]
now i’d like to find :b and :f when if i ask for children of :a.
are there any examples for doing that?At the moment, Meander doesn’t have an obvious solution to this and I would probably index the data prior to doing any queries over it.
(let [data [{:name :a, :parent :root}
{:name :c, :parent :root}
{:name :d, :parent :c}
{:name :e, :parent :d}
{:name :f, :parent :b}
{:name :b, :parent :a}]
index (group-by :parent data)
parent :root
plain-clj (fn f [parent]
(map (fn [?node]
(cons ?node (flatten (f (:name ?node)))))
(get index parent)))
meander (fn f [parent]
(m/search [parent index]
[?parent {?parent (m/scan {:name ?name :as ?node})}]
(cons ?node (flatten (f ?name)))))]
(= (plain-clj parent)
(meander parent))
;; => true
(meander parent))
;; =>
(({:name :a, :parent :root}
{:name :b, :parent :a}
{:name :f, :parent :b})
({:name :c, :parent :root}
{:name :d, :parent :c}
{:name :e, :parent :d}))
ah ok, thank you. I wound up adding a :children array and indexing by :id so that i can do proper tail recursion like this:
(defn dependants [n traces]
(let [roots (find-roots n traces)]
(loop [[x & xs :as nodes] roots
seen []]
(cond
(empty? nodes) seen
(empty? (:children x)) (recur xs (conj seen x))
(coll? (:children x)) (recur
(into xs (vals (select-keys traces (:children x))))
(conj seen x))))))
just out of interest and if you’ve got a minute, would the “equivalent” meander just replace the cond call?I’m not sure I understand the question.
I don’t think the second clause of the cond is necessary though. If (:children x) is empty? the call to select-keys will return an empty map, vals will return an empty sequence, thus making the into have no effect. But, of course, overall that raises the cost slightly.
you are correct, I haven’t cleaned it up yet since I’m still playing with other features. my question was if a meander alternative to the handwritten function would look more or less the same with (m/search … (m/scan …) (recur …)) or if it’s going to be easier and give some benefits. I’m trying to figure out if I should try to make most of the query’s on that data meander search calls for consistency or if it makes more sense to mix and match plain clojure with it.
So, at the moment, recur can’t be used on the right side of any meander expression like search. This is because the compiler will often emit functions that wrap the right side.
In terms of comparing plain Clojure or Meander, that is honestly up to you. Meander is often a suitable replacement for nested map, mapcat, filter, etc. type of work. It is especially designed for pulling apart data and matching patterns in data that would be a pain to write by hand in Clojure.
If you use it for a little while, you’ll figure out where it helps you and where it is just a distraction. 🙂