Clojure:将集合转换为多棵树
Clojure: transform collection into multiple trees
所以我有一个评论数据库 table,我学会了如何使用 WITH RECURSIVE
到 return 一个主题的所有评论,作为一棵树。但是,因为它是 SQL,所以它只是 return 作为列表编辑。
当我执行查询时,这些是我得到的结果(级别不是 table 上的列,它是在查询收集结果时计算的):
[
{
: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,
}
]
我想弄清楚的是如何转动多棵树,这样我就得到了这样的结果:
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'
使用这个函数只会得到第一棵树(根节点为 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)})))
关于如何修改该函数或更改我传递的内容以便最终得到多棵树的任何想法或提示?
你可以定义一个 make-trees 函数:
(defn make-trees [id coll]
(map
(fn [node] {:node node :children (make-trees (node :id) coll)})
(filter #(= (% :parent_id) id) coll)))
这样调用:
(make-trees nil YOUR_COLL)
如果您可以依赖 :level
条目,那么它可以作为键序列的来源与 assoc-in
一起使用。然后,您可以非常简单地使用 reduce
和基于 assoc-in
:
构建的小型 lambda 来执行 @coredump 提到的方法和专用根节点
(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)))
这里我在 reduce
的结果上使用 vals
再次丢弃外根映射,但这是可选的。
编辑正则表达式问题:
如果您想要使用它的真实数据实际上在 :level
中有 UUID,那么我们将需要使用更合适的正则表达式。以上将把十进制数字的任何部分视为一个 ID。 Using these answers 我们可以收集 :level
字符串中的所有 UUID。
我 reworked your example data 用一些随机的 UUID 代替你给的数字。使用上面的 Gajus Kuizinas 的正则表达式 link 然后我做了这些重新定义:
(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)))
获取输出
({: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 {}}}}}})
事实证明@coredump 的想法是正确的。通过让顶级评论的父 ID 成为主题,然后我可以使用 clojure。zip/zipper 很容易地构建树。
所以我有一个评论数据库 table,我学会了如何使用 WITH RECURSIVE
到 return 一个主题的所有评论,作为一棵树。但是,因为它是 SQL,所以它只是 return 作为列表编辑。
当我执行查询时,这些是我得到的结果(级别不是 table 上的列,它是在查询收集结果时计算的):
[
{
: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,
}
]
我想弄清楚的是如何转动多棵树,这样我就得到了这样的结果:
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'
使用这个函数只会得到第一棵树(根节点为 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)})))
关于如何修改该函数或更改我传递的内容以便最终得到多棵树的任何想法或提示?
你可以定义一个 make-trees 函数:
(defn make-trees [id coll]
(map
(fn [node] {:node node :children (make-trees (node :id) coll)})
(filter #(= (% :parent_id) id) coll)))
这样调用:
(make-trees nil YOUR_COLL)
如果您可以依赖 :level
条目,那么它可以作为键序列的来源与 assoc-in
一起使用。然后,您可以非常简单地使用 reduce
和基于 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)))
这里我在 reduce
的结果上使用 vals
再次丢弃外根映射,但这是可选的。
编辑正则表达式问题:
如果您想要使用它的真实数据实际上在 :level
中有 UUID,那么我们将需要使用更合适的正则表达式。以上将把十进制数字的任何部分视为一个 ID。 Using these answers 我们可以收集 :level
字符串中的所有 UUID。
我 reworked your example data 用一些随机的 UUID 代替你给的数字。使用上面的 Gajus Kuizinas 的正则表达式 link 然后我做了这些重新定义:
(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)))
获取输出
({: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 {}}}}}})
事实证明@coredump 的想法是正确的。通过让顶级评论的父 ID 成为主题,然后我可以使用 clojure。zip/zipper 很容易地构建树。