So I've got a database table for comments, and I learned how to use WITH RECURSIVE
to return all the comments for a topic, as a tree. However, because it's SQL, it's just returned as a list.
When I execute my query, these are the results I get back ( level is not a column on the table, it's calculated by the query as it gathers results ):
[
{
:id "1"
:parent_id nil,
:content "This is another top-level comment",
:level "1",
:rating 0,
}
{
:id "2"
:parent_id "1",
:content "What a comment!",
:level "1 -> 2",
:rating 0,
}
{
:id "4"
:parent_id "2",
:content "Trying to see how trees work",
:level "1 -> 2 -> 4",
:rating 0,
}
{
:id "3"
:parent_id "2",
:content "No idea how this will turn out",
:level "1 -> 2 -> 3",
:rating 0,
}
{
:id "5"
:parent_id nil,
:content "This is a top-level comment",
:level "5",
:rating 0,
}
{
:id "9"
:parent_id "5",
:content "This is yet another testing comment",
:level "5 -> 9",
:rating 0,
}
{
:id "8"
:parent_id "7",
:content "It sure is!",
:level "5 -> 7 -> 8",
:rating 0,
}
{
:id "7"
:parent_id "5",
:content "This!",
:level "5 -> 7",
:rating 0,
}
{
:id "6"
:parent_id "5",
:content "Hey look at me",
:level "5 -> 6",
:rating 0,
}
]
What I'd like to figure out is how to turn multiple trees, so that I end up with something like so:
1 'This is another top-level comment'
↳ 2 'What a comment!'
↳ 4 'Trying to see how trees work'
↳ 3 'No idea how this will turn out'
5 'This is a top-level comment'
↳ 9 'This is yet another testing comment'
↳ 7 'This!'
↳ 8 'It sure is!'
↳ 6 'Hey look at me'
Using this function only gets me the first tree ( the one with root node of id 1 ):
(defn make-tree
([coll] (let [root (first (remove :parent coll))]
{:node root :children (make-tree root coll)}))
([root coll]
(for [x coll :when (= (:parent_id x) (:id root))]
{:node x :children (make-tree x coll)})))
Any ideas or hints on how I could either modify that function, or change what I'm passing in so that I end up with multiple trees?
you can define a make-trees function:
(defn make-trees [id coll]
(map
(fn [node] {:node node :children (make-trees (node :id) coll)})
(filter #(= (% :parent_id) id) coll)))
called like this:
(make-trees nil YOUR_COLL)
If you can rely on the :level
entry, that can work OK as a source of key sequences to use with assoc-in
. You can then do the approach @coredump mentioned with a dedicated root node pretty simply using reduce
and a small lambda built on assoc-in
:
(defn- key-seq [comment]
(->> comment
:level
(re-seq (re-pattern "\\d+"))
(interpose :children)))
(defn list->forest [comments]
(vals (reduce (fn [root comment]
(assoc-in root (key-seq comment) {:node comment :children {}}))
{}
comments)))
Here I use vals
on the result of the reduce
to discard the outer root map again, but that's kinda optional.
If the real data you want to use this on actually has UUIDs in the :level
then we'll need to use a more appropriate regex. The above will treat any section of decimal digits as an ID. Using these answers we can collect all the UUIDs in the :level
string instead.
I reworked your example data with some random UUIDs in place of the numbers you gave. Using Gajus Kuizinas' regex from the above link I then did these redefinitions:
(ns comment-forest
(:require [clojure.walk :refer [postwalk]]
[clojure.pprint :refer [pprint]])
(:import java.util.UUID))
(defn- key-seq [comment]
(->> comment
:level
(re-seq (re-pattern "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89aAbB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}"))
(map #(UUID/fromString %))
(interpose :children)))
;;This is just to print the trees with less unnecessary detail
(defn- prune [value]
(if
(or
(not (map? value))
(every? (partial contains? value) [:node :children])
(every? #(= UUID (type %)) (keys value)))
value
(select-keys value [:id :content])))
(pprint (map (partial postwalk prune) (list->forest querylist)))
to get output
({:node
{:content "This is a top-level comment",
:id "ee9a2671-b47e-40ef-994f-a7b0fa81d717"},
:children
{#uuid "f28a159c-de66-4712-9cb8-e1841afeebf6"
{:node
{:content "Hey look at me",
:id "f28a159c-de66-4712-9cb8-e1841afeebf6"},
:children {}},
#uuid "d3fccc58-5e59-486d-b784-c54f0e4698b1"
{:node
{:content "This!", :id "d3fccc58-5e59-486d-b784-c54f0e4698b1"},
:children
{#uuid "e6387f7d-4f29-42c9-a386-7f799341f48f"
{:node
{:content "It sure is!",
:id "e6387f7d-4f29-42c9-a386-7f799341f48f"},
:children {}}}},
#uuid "3de27950-7340-49d1-a28e-54ad2e4ea0f1"
{:node
{:content "This is yet another testing comment",
:id "3de27950-7340-49d1-a28e-54ad2e4ea0f1"},
:children {}}}}
{:node
{:content "This is another top-level comment",
:id "fdc8a8b9-19c7-4fad-963d-2c2ca0bcbe8a"},
:children
{#uuid "b17bc5b8-9968-48ce-8ff3-83c8123cd327"
{:node
{:content "What a comment!",
:id "b17bc5b8-9968-48ce-8ff3-83c8123cd327"},
:children
{#uuid "1cee5390-e810-49b7-ad10-098bfbe03ab2"
{:node
{:content "No idea how this will turn out",
:id "1cee5390-e810-49b7-ad10-098bfbe03ab2"},
:children {}}}}}})
Turns out @coredump had the right idea. By having the top-level comments have their parent-id be the topic, then I can just use clojure.zip/zipper to build a tree pretty easily.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.