@pranav.ram7 I think this should do the trick.
(m/rewrite input
{:id ?id
:has-element+ (m/scan ?nested-element)}
[[:id ?id :has-element+ ?nested-element]
;; Recursively rewrite the nested element
& (m/cata ?nested-element)]
_ ;; Default case, empty vector.
[])
@noprompt Fantastic, this worked great. Thank you for the help and library!
Follow up - is there a way to spread the has-element+ s into their own items? so if a parent has multiple has-element+ items, they get their own entry in relation to the parent. Also try and remove the nested has-element+ from the RHS for each pair
Example?
So for something like so:
{:name "Interest",
:id "Interest",
:db/ident "Interest",
:type "Label",
:has-element
[{:name "Credit Interest Parameters",
:id "Credit Interest Parameters",
:db/ident "Credit Interest Parameters",
:type "Label",
:has-element
[{:mandatory true,
:path ["Interest" "Credit Interest Parameters" "Balance Base"],
:name "Balance Base",
:apiName nil,
:fixedAllowedValues nil,
:json-schema
{:type "object", :properties {:value {:enum ["Daily Balance" "Average Daily Balance"]}}, :required ["value"]},
:size nil,
:dataType "LOV",
:id "93b778a4-99cd-4839-95e6-90a965d61e06",
:db/ident "93b778a4-99cd-4839-95e6-90a965d61e06",
:path-string "Interest|Credit Interest Parameters|Balance Base",
:allowedValues ["Daily Balance" "Average Daily Balance"]}
{:mandatory true,
:path ["Interest" "Credit Interest Parameters" "Balance Base 2"],
:name "Balance Base 2",
:apiName nil,
:fixedAllowedValues nil,
:json-schema
{:type "object", :properties {:value {:enum ["Daily Balance" "Average Daily Balance"]}}, :required ["value"]},
:size nil,
:dataType "LOV",
:id "7591f44f-da3a-464f-bd3a-d0773f319aa3",
:db/ident "7591f44f-da3a-464f-bd3a-d0773f319aa3",
:path-string "Interest|Credit Interest Parameters|Balance Base 2",
:allowedValues ["Daily Balance" "Average Daily Balance"]}],
:path ["Interest" "Credit Interest Parameters"],
:path-string "Interest|Credit Interest Parameters"}
{:name "Debit Interest Parameters",
:id "Debit Interest Parameters",
:db/ident "Debit Interest Parameters",
:type "Label",
:has-element
[{:mandatory true,
:path ["Interest" "Debit Interest Parameters" "Balance Base"],
:name "Balance Base",
:apiName nil,
:fixedAllowedValues nil,
:json-schema
{:type "object", :properties {:value {:enum ["Daily Balance" "Average Daily Balance"]}}, :required ["value"]},
:size nil,
:dataType "LOV",
:id "9c414ea9-1080-41fc-9b54-51faebd3fac7",
:db/ident "9c414ea9-1080-41fc-9b54-51faebd3fac7",
:path-string "Interest|Debit Interest Parameters|Balance Base",
:allowedValues ["Daily Balance" "Average Daily Balance"]}
{:mandatory true,
:path ["Interest" "Debit Interest Parameters" "Balance Base 2"],
:name "Balance Base 2",
:apiName nil,
:fixedAllowedValues nil,
:json-schema
{:type "object", :properties {:value {:enum ["Daily Balance" "Average Daily Balance"]}}, :required ["value"]},
:size nil,
:dataType "LOV",
:id "ec66690b-00b6-402c-b943-962e0d7127a3",
:db/ident "ec66690b-00b6-402c-b943-962e0d7127a3",
:path-string "Interest|Debit Interest Parameters|Balance Base 2",
:allowedValues ["Daily Balance" "Average Daily Balance"]}],
:path ["Interest" "Debit Interest Parameters"],
:path-string "Interest|Debit Interest Parameters"}],
:path ["Interest"],
:path-string "Interest"}
It should output:
[
{:id "Interest" :has-element {:id "Credit Interest Parameters" & rest-map-except-nested-has-element}}
{:id "Credit Interest Parameters" :has-element {:id "Balance Base" & rest-map-except-nested-has-element}}
{:id "Credit Interest Parameters" :has-element {:id "Balance Base 2" & rest-map-except-nested-has-element}}
{:id "Interest" :has-element {:id "Debit Interest Parameters" & rest-map-except-nested-has-element}}
{:id "Debit Interest Parameters" :has-element {:id "Balance Base" & rest-map-except-nested-has-element}}
{:id "Debit Interest Parameters" :has-element {:id "Balance Base 2" & rest-map-except-nested-has-element}}
]
Note that the above tree structure could have any depth with the key :has-element and it should flatten the structure in terms of every 2 nodes (parent :has-element child)This is how I'm solving it in plain clojure with walk:
(def output (atom []))
(do (reset! output [])
(walk/prewalk (fn [node]
(when (and (map? node) (contains? node :has-element))
(swap! output conj
(map #(do {:id (:name x)
:has-element (dissoc % :has-element)})
(:has-element node)))
)
node) input))@pranav.ram7 Ah, then you probably want to use m/search instead.
(m/search input
{:id ?id
:has-element (m/scan {:has-element _ & ?without-children})}
{:id ?id :has-element ?without-children}
{:has-element (m/scan (m/cata ?child))}
?child)
You have a disjunction in disguise. You want the result with the {:id, :has-element} or you want the transformed {:id, :has-element} from the children.Another thing to consider in the future for problems like this is how you might use tree-seq. You can use tree-seq to extract the branches and then you can post process them to do the remaining work:
;; With `for`
(let [branch? (fn [x]
(and (map? x)
(contains? x :id)
(contains? x :has-element)))
children (fn [x]
(filter branch? (:has-element x)))]
(for [branch (tree-seq branch? children input)
:let [id (:id branch)
children (:has-element branch)]
child children
:let [without-children (dissoc child :has-element)]]
{:id id
:has-element without-children}))
;; With `mapcat`
(let [branch? (fn [x]
(and (map? x)
(contains? x :id)
(contains? x :has-element)))
children (fn [x]
(filter branch? (:has-element x)))]
(mapcat (fn [branch]
(let [id (:id branch)
children (:has-element branch)]
(map (fn [child]
(let [without-children (dissoc child :has-element)]
{:id id
:has-element without-children}))
children)))
(tree-seq branch? children input)))
I might also advise against using something like prewalk if possible in favor of something tuned specifically for your use case. prewalk walks everything and this is not often what people want. In this case, we only need to traverse the objects that meet our branch? criteria. If we use postwalk, we'll end up traversing all of the values in the root node including stuff we know we know is irrelevant.
A similar, heavy-handed approach using Meander would be to use the m/$ pattern in conjunction with m/search.
(m/search input
(m/$ {:id ?id :has-element (m/scan {:has-element _ & ?without-children})})
{:id ?id :has-element ?without-children})
This will produce the same result set as the previous solutions at the cost of traversing the whole input.Fantastic, thank you! I had to put the project on hold and just returned to it. This worked great and thank you for the advise on using tree-seq to avoid traversing the whole tree.
Would you be able to elaborate on how this works, maybe just high level what m/scan does for the structure? https://clojurians.slack.com/archives/CFFTD7R6Z/p1689091085768469?thread_ts=1688848556.713519&cid=CFFTD7R6Z