“rerootable”纯粹的功能树数据结构

时间:2015-02-22 19:34:19

标签: clojure tree functional-programming purely-functional phylogeny

我最近由Joseph Felsenstein购买了Inferring Phylogenies,这是一本关于推断系统发育树的数学和计算方法的好书,并且一直在实施它所描述的一些算法。

具体来说,我有兴趣在具有持久性数据结构的功能设置中使用,因为许多方法涉及在可能的树木空间中行走,并且很便宜地记住我们所处的历史。通过结构共享(a what在this blog post中用“世界”做什么),轻松缓存先前为子树等计算的值。

这个问题是很多方法涉及“连根”树,我无法弄清楚如何以纯粹的功能方式廉价地做。基本上我需要一些方法来捕捉以下每一个的想法(使用clojure表示法,将树表示为向量):

[:a [:b [:c :d]]] 
[:b [:a [:c :d]]]
[:a [:b [:d :c]]]
[:b [:a [:d :c]]]
[[:a :b] [:c :d]] 
[[:c :d] [:a :b]]
[:c [:d [:a :b]]]
[:d [:c [:a :b]]]
[:c [:d [:b :a]]]
[:d [:c [:b :a]]]

代表相同的数据,只是根位置不同;它们各自代表无根树:

a   b
 \ /
  |
 / \
c   d

我希望能够使用拉链导航到这些树中的一个,然后调用一个函数reroot,它将返回一个新的树,该树被压缩,使得根位于当前loc

在书中,Felsenstein描述了一个便宜的可重新连根的树的数据结构,它看起来像下面匆匆制作的图

terrible diagram

其中圆圈是结构,箭头是指针。结构的环是树上的内部节点,一旦我们有一个引用,我们就可以通过做一些指针交换来移动那里的根。不幸的是,这是一个变异操作,需要相互参考,这两者在纯粹的功能设置中是不可能的。

我觉得应该有一种方法可以使用拉链来做我想做的事情,但我一直在玩clojure.core/zip一段时间而无处可去。

有没有人知道这样的事情的实现,或者对我应该阅读的事情提出建议/我应该看看的文件/如何做到的想法?

谢谢!

2 个答案:

答案 0 :(得分:2)

jvm实际上并没有让我们访问指针,因此我们可以直接操作。但我们确实有一些选择来表示双重链接结构。

这看起来很像图形,对于像这样的稀疏图形,经典表示是adjacency list。邻接列表的一个优点是它们通过名称取消引用而不是依赖于指针/对象标识,因此我们可以在结构中表达任意循环或自引用路径而无需任何变异。

按字母顺序从左到右/从上到下命名节点:

{:a [:c]
 :b [:d]
 :c [:a :d :e]
 :d [:b :c :e]
 :e [:c :d :g]
 :f [:h]
 :g [:e :h :i]
 :h [:f :g :i]
 :i [:g :h]}

网络中的元素按名称查找,从该元素出来的箭头由向量表示为关联值。遍历可以实现为递归函数,在每次迭代时查找节点以进行步骤。 “root”只是用于开始遍历的元素(图表中为:i)。

可以使用conjupdate-inassoc等进行各种插入/拆分重排,因为哈希映射文字是常规的clojure持久数据结构。

答案 1 :(得分:1)

无根树是具有以下特征的图:

  • 它是对称/无向的 - 它是它自己的逆。
  • 它紧密相连 - 你可以从任何地方到处都是。
  • 回到你来自哪里的唯一方法是回溯你的 脚步。

表示图形的标准方法是给出每个节点的邻居集合的映射。这就是the standard clojure graph library的作用,尽管它的操作在很大程度上多余的defstruct后面被遮挡了。

对于您的示例,地图是

{:I #{:a :b :c :d}, :a #{:I}, :b #{:I}, :c #{:I}, :d #{:I}}

这是无向图表,当它是自己的inverse时,其中

(defn inverse [g]
  (apply merge-with clojure.set/union
         (for [[x xs] g, y xs] {y #{x}})))

您无需在任何地方执行任何操作。正如@noisesmith所说,根只是你开始枚举的节点。从图表来看,这与Felsenstein的数据结构同样如此。

如图所示,如果仅内部节点是多重连接的,则可以通过直接从每个外部节点映射到其唯一邻居来节省一些空间。你的例子将成为

{:I #{:a :b :c :d}, :a :I, :b :I, :c :I, :d :I}

或许更好地表达为两张地图:

{:internals {:I #{:a :b :c :d}}, :externals {:a :I, :b :I, :c :I, :d :I}}