从Clojure中的向量构建树
我正在做我的第一个Clojure项目。我在计算如何基于如下输入构建树时遇到了一些问题:从Clojure中的向量构建树,clojure,tree,Clojure,Tree,我正在做我的第一个Clojure项目。我在计算如何基于如下输入构建树时遇到了一些问题: ["A B" "A C" "C D" "D E" "A F" "F G"] '(A (B) (C (D (E)) (F (G))) (defn build-tree [func tree parent child] (map (fn [i] (if (seq? i) build-tree (func i parent child)
["A B" "A C" "C D" "D E" "A F" "F G"]
'(A (B) (C (D (E)) (F (G)))
(defn build-tree
[func tree parent child]
(map (fn [i] (if (seq? i)
build-tree
(func i parent child)))))
输出应该是这样的:
["A B" "A C" "C D" "D E" "A F" "F G"]
'(A (B) (C (D (E)) (F (G)))
(defn build-tree
[func tree parent child]
(map (fn [i] (if (seq? i)
build-tree
(func i parent child)))))
我不知道如何开始这样做。例如,当涉及到命令式编程时,我会使用嵌套循环来查找这个关系是否已经存在,当它不存在时,我会找到父元素并将子元素附加到它。但据我所知,函数式编程将使用另一种方法,我将递归地遍历向量中的所有元素,而不是改变现有的树,我将构建一个新的树
我不确定这是否有效,但我有一个函数如下所示:
["A B" "A C" "C D" "D E" "A F" "F G"]
'(A (B) (C (D (E)) (F (G)))
(defn build-tree
[func tree parent child]
(map (fn [i] (if (seq? i)
build-tree
(func i parent child)))))
我确信这个问题源于我对Clojure和函数式编程的不熟悉,我希望有人能解释使用递归的最佳方法并构建这棵树。您的输出看起来像一个关联结构,您为什么在这里使用列表而不是映射 此外,是否保证排序与示例中的树结构相匹配?我想不会吧。我认为这里不使用递归比较容易 让我们将输入解析为一个邻接列表,如:
(reduce
(fn[g edge]
(let [[from to]] (map keyword (str/split " " edge))
(update g from #(conj % to))
{}
["A B" "B C"])
应输出:
{A[B C F],,}
如果您愿意,可以使用它创建树作为列表
我无法测试这一点,因为我现在在手机中,所以请原谅我的错误。:) 您可能会收到一些比下面我的答案短得多的答案。我决定教你一些鱼,而不是展示一个简短的程序,我将带你通过一个系统的方法来解决这类问题。您必须填写一些空白,包括将数据转换为更令人满意的形式。这些都是只会分散注意力的小问题 您需要做的第一件事是将输出分解为构造步骤。这些步骤应该与输出所表示的抽象类型相匹配。在您的例子中,输出的具体类型是一个列表,但它实际上表示一棵树。这些是不同的抽象类型-列表有头和尾,就是这样,但树有节点,节点可能有子节点。您需要考虑构建不同类型节点的抽象构造函数,而不是偶然选择的特定结构来表示您的抽象类型—在您的示例中是一个列表 假设有两个构造函数
(defn ->branch
[id kids]
...)
(defn ->leaf
[id]
...)
当然,我们假设每个孩子都是一棵树,也就是说,一根树枝或者一片叶子。换句话说,每个孩子
都应该是->分支
或->叶
的结果
因此,构建任何树的唯一方法是拥有一个嵌套的构造函数调用链。这就是我所说的“将输出分解为构建步骤”的意思。在您的案例中,这意味着以下链条:
(->branch :A [(->leaf :B) (->branch :C [(->branch :D (->leaf :E))]) (->branch :F [(->leaf :G)])])
(我将对节点ID使用关键字而不是符号,否则我们将陷入绑定/引用细节的困境。)
在这里停一下,感受一下功能性风格和命令式风格之间的区别。您自己概述了命令式样式—您认为应该通过找到正确的位置并在那里添加新节点来更新树。在功能性风格中,您考虑的是创建一个值,而不是更新另一个值。(当然,从技术上讲,没有什么可以阻止您在命令式语言中执行相同的操作——比如使用对构造函数的嵌套调用,或者以这种方式使用静态工厂,这不是自然发生的事情。)
要获得作为列表的特定树表示,只需填写上面的->branch
和->leaf
的实现,这非常简单。(留给您作为练习。)
现在回到树的建造。编写构建树的函数的任务实际上是从输入数据创建一系列构造函数调用。所以你需要了解两件事:
- 何时调用哪个构造函数(此处,何时调用
以及何时调用->branch
)->leaf
- 如何获取构造函数的参数(
用于其中一个,而id
用于kids
)->branch
(defn ->tree
[adj-list node]
...)
adj列表
是您的输入-即使您试图将其伪装为字符串列表,这也是一个错误,我们将以这种方式处理它(如注释中建议的@SamEstep。)作为练习,让您将输入形式转换为邻接列表
因此,在我们的构造函数中,节点
使用什么作为id
应该很清楚。但是它是分支还是叶子呢?答案取决于我们是否在adj列表
中有节点
的直接后代,因此显然我们需要一个函数
(defn descendants
[adj-list node]
...)
它返回节点的直接后代的ID列表(可能为空)。(作为练习)因此,我们可以根据列表是否为空来决定是否调用->branch
或->leaf
,如
(if-let [kid-ids (descendants node)]
(->branch node ...)
(->leaf node))
好的,那么我们需要将这些…
提供给->分支机构。它们是儿童ID吗?不,它们必须是树,不是ID,不是树枝或树叶本身。换句话说,我们需要首先对它们调用->tree
。看到了吗我们到达了递归点,它是自然发生的(我希望是这样)。在Clojure中,对序列的每个元素调用函数是由map
完成的,它返回一个结果序列,这正是我们需要的:
(if-let [kid-ids (descendants adj-list node)]
(->branch node (map ->tree kid-ids)
(->leaf node))
除了->tree
需要一个额外的参数,adj list
。我们可以使用匿名函数o