【问题标题】:Clojure: transform collection into multiple treesClojure:将集合转换为多棵树
【发布时间】:2016-01-27 14:31:05
【问题描述】:

所以我有一个 cmets 的数据库表,并且我学会了如何使用 WITH RECURSIVE 以树的形式返回一个主题的所有 cmets。但是,因为是 SQL,所以只是作为列表返回。

当我执行查询时,这些是我返回的结果(级别不是表上的列,它是由查询在收集结果时计算的):

[
 {
  :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)})))

关于如何修改该函数或更改我传入的内容以得到多棵树的任何想法或提示?

【问题讨论】:

  • 你有一片森林,而不是一棵树。您可以有一个特殊节点,所有具有nil 父级的 cmets 都可以附加到该节点。
  • 是的,我也想到了。我正在尝试修改查询和代码,以便顶级 cmets 将 post id 作为父 id,但 make-tree 函数似乎不喜欢那样。我认为这是我可以通过让查询也选择帖子并将其作为树中的第一个节点来解决的问题。

标签: clojure tree


【解决方案1】:

事实证明@coredump 的想法是正确的。通过让顶级 cmets 将其 parent-id 作为主题,然后我可以使用 clojure.zip/zipper 轻松构建树。

【讨论】:

    【解决方案2】:

    如果您可以依赖: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 的正则表达式进行了这些重新定义:

    (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 {}}}}}})
    

    【讨论】:

    • 对使用 UUID 的实际数据效果不佳
    • 如在正则表达式 doesn't find UUIDs 或?
    • 我这台电脑上没有输出,但基本上它也把部分uuid变成了子节点。
    【解决方案3】:

    你可以定义一个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)
    

    【讨论】:

      猜你喜欢
      • 2014-11-02
      • 1970-01-01
      • 2011-05-17
      • 2012-03-05
      • 1970-01-01
      • 1970-01-01
      • 2010-12-19
      • 2020-02-05
      • 1970-01-01
      相关资源
      最近更新 更多