【问题标题】:Clojure tree node based on depthClojure树节点基于深度
【发布时间】:2016-09-06 02:20:57
【问题描述】:

我一直在努力解决递归问题,但我的想法已经不多了。基本上,我有一个看起来像这样的树表示:

{1 {:root nil} 2 {:root 1} 3 {:root 1} 4 {:root 2} 5 {:root 1} 6 {:root 4}}

我必须从最后一棵树中构建一棵新树,以指示父/子关系级别。一个有效的输出是:

{ 1 [3 1] 2 [1] 3 [] 4 [1] 5 [] 6 [] }

每个节点都有一个按关系级别排列的项目计数数组。所以节点 1 有 3 个直接子代 (2 3 5) 和一个孙子 (4)。节点 2 只有一个孩子 (4),节点 4 有一个直接孩子 (6),其他都是空的(没有孩子)。

我发现了一些问题like this one 确实有帮助,但并不是我想要的。我也是函数编程的新手,我们将不胜感激。

【问题讨论】:

  • 不确定我是否理解您的树表示。 :root:parent 键代表什么?我希望只为每个节点找到一个直接父节点,为什么需要额外的密钥?
  • 这是一个错字,关键是":root"。每个节点都有对其父节点 (root) 的引用。谢谢!

标签: clojure tree


【解决方案1】:

我将假设您上面的示例输出中存在错误,并且确实应该是:

{ 1 [3 1 1] 2 [1 1] 3 [] 4 [1] 5 [] 6 [] }

您的示例输出没有说明 6 是 1 的曾孙和 2 的孙。

我将在这里详细说明一个解决方案。我们将从编写一个函数开始,给定一棵树和该树中的一个顶点,计算从该顶点到树顶部的路径:

(defn path-to-top [tree v]
    (if (nil? v)
        '()
        (cons v (path-to-top tree (:root (get tree v))))))

接下来,让我们编写一个函数,它采用从顶点到树顶的路径,并与每个顶点相关联on该顶点到起始顶点的距离:

(defn steps-indexed-path
    ([upward-path steps]
        (if (= upward-path '())
            '()
            (cons [(first upward-path) steps] (steps-indexed-path (rest upward-path) (+ steps 1)))))
    ([upward-path]
        (steps-indexed-path upward-path 0)))

第一个函数返回一个顶点列表,这个函数返回一个向量列表,其中第一个条目是一个顶点,第二个条目是从路径上的第一个顶点到给定顶点的步数。

好的,当我们将此函数应用于树中的每个顶点时,我们将(以某种嵌套形式)为每个顶点 vv 的每个后代 w 提供数据 [v <# steps from v to w>] .对于这些数据中的每一个,我们应该在最终解决方案中与v 关联的向量的<# steps from v to w> 分量上加1。在我们进入向量阶段之前,让我们将级别与计数相关联:

(defn count-descendants [tree]
    (let [markers (reduce concat '() (map steps-indexed-path (map (partial path-to-top tree) (keys tree))))]
        (reduce (fn [counter [vertex generation]] (assoc counter vertex (assoc (get counter vertex {}) generation (+ (get (get counter vertex {}) generation 0) 1)))) {} markers)))

这会产生一个hash-map,其键是v 的顶点,并且对应于每个顶点v 的值是另一个hash-map,其中的键是该顶点的后代的不同可能代树,值是每一代的后代数量。

我们现在要做的就是把上一个函数的输出变成你指定的格式:

(defn sanitize-descendant-counts [association] 
    (let [max-depth (apply max (keys association))]
        (map (fn [i] (get association i 0)) (range 1 (+ max-depth 1)))))

(defn solve-problem [tree]
    (let [descendant-counts (count-descendants tree)]
        (apply merge (map (fn [v] (hash-map v (vec (sanitize-descendant-counts (get descendant-counts v))))) (keys descendant-counts)))))

这是我在您的示例上运行此代码时得到的输出:

{1 [3 1 1], 4 [1], 6 [], 3 [], 2 [1 1], 5 []}

您可以access all the code here,包括您需要在示例上运行的内容。希望对您有所帮助!

【讨论】:

    【解决方案2】:

    我将尝试概述一种可能的方法,强调递归核心,并忽略较小的细节。这些较小的细节有很多,其中一些并不完全是微不足道的,但它们与递归本身无关,只会使答案变得混乱。

    让我们从树表示的细节中抽象出来。将树视为节点的集合,其中每个节点可能是叶子(没有子节点)或分支。假设我们有两个函数branch?children。要么接收一个参数 - 一个节点。 branch? 是一个意义明显的谓词,children 返回节点的子节点序列。 (它与tree-seq 核心函数所期望的合约相同。)我留给你编写branch?children。 (您可能希望更改您的树表示,以便更轻松地编写这些函数。)

    让我们尝试创建一个函数levels,它给定一个节点将返回一系列后代的序列 - 子代、孙代等。所以我们期待你的树

    (levels 1)
    ;; => (3 1 1)
    (levels 2)
    ;; => (1 1)
    

    (顺便说一句,你打错了。节点 1 有一个孙子 - 它是 6)

    这是核心 - levels

    (defn levels
      [node]
      (if (branch? node)
        (cons (count (children node)) (sum-up-levels (map levels (children node))))
        []))
    

    这是递归的核心。 基本情况是叶子-当branch?返回false时,我们知道没有子级,所以级别是空的-[]。否则,我们计算孩子和cons 下面总结 级别的那个数字(即添加到列表中)。求和意味着按级别对数字求和 - 孩子总数,然后是孙子总数等等。这里我们有递归 - 我们下降到孩子,使用map为每个孩子递归调用levels

    sum-up-levels 函数对代码来说有点烦人。我留下了很多东西要你填写,我可能只是在这里给出我的代码(当然不是最短的,但我没有更多的时间来完善它。)

    (defn reduce-through
       [f & colls]
       (when-let [colls (seq (filter seq colls))]
         (cons (reduce f (map first colls))
               (apply reduce-through f (map rest colls)))))
    
    (defn sum-up-levels
       [levels]
       (apply reduce-through + levels))
    

    在定义levels 之后,很容易以您需要的形式获得结果。试试看(提示 - 使用tree-seq。)

    【讨论】:

    • Yuri,感谢您抽出宝贵时间详细回答。我想我明白了。我打算做的第一件事是将我的树表示从映射更改为如下所示的序列:'(1 (2 (4 (6)) 3 5) 然后使用树序列构建缺失的函数 (分支?和孩子)。你怎么看?
    • 是的,我认为使用这样的序列应该更简单。在这种情况下,branch?children 相当简单。 (您不需要tree-seq 来构建这些函数,而是tree-seq 可以使用它们将一棵树变成一个序列,这样处理起来会非常简单。)
    • 再次感谢。使用我开始使用的数据结构效果很好:-) 但是我很好奇如何使用表示为'(1 (2 (4 (6)) 3 5) 的树来编写子函数。对于第一棵树表示,代码如下所示:(defn children [node] (filter (fn [[x, y]] (= node (:root y))) tree))
    • 别想了,你可以用一个列表(或子列表)来表示一个分支节点,那么branch?就是list?,而children就是rest .
    • 当然在这种情况下你会使用(levels a-list)而不是(levels 2)
    【解决方案3】:
    (defn children-depths [parents]
      (let [children ; let's first build an inverted index
            (reduce-kv (fn [children node {parent :root}]
                         (update children parent conj node))
              {} parents)
            depths (fn depths [node]
                     (if-some [nodes (children node)]
                       (into [(count nodes)]
                         ; pads and sums
                         (reduce #(map + (concat %1 (repeat 0)) (depths %2))
                           nil nodes))
                       []))] ; no descendants -> empty 
        (into {} (for [node (keys parents)] [node (depths node)]))))
    
    => (children-depths {1 {:root nil} 2 {:root 1} 3 {:root 1} 4 {:root 2} 5 {:root 1} 6 {:root 4}})
    {1 [3 1 1], 2 [1 1], 3 [], 4 [1], 5 [], 6 []}
    

    一个明显的改进是避免重新计算孩子的深度。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-21
      • 1970-01-01
      • 1970-01-01
      • 2011-06-20
      • 2020-07-19
      • 1970-01-01
      • 2021-01-02
      • 2021-06-27
      相关资源
      最近更新 更多