我最近由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描述了一个便宜的可重新连根的树的数据结构,它看起来像下面匆匆制作的图
其中圆圈是结构,箭头是指针。结构的环是树上的内部节点,一旦我们有一个引用,我们就可以通过做一些指针交换来移动那里的根。不幸的是,这是一个变异操作,需要相互参考,这两者在纯粹的功能设置中是不可能的。
我觉得应该有一种方法可以使用拉链来做我想做的事情,但我一直在玩clojure.core/zip
一段时间而无处可去。
有没有人知道这样的事情的实现,或者对我应该阅读的事情提出建议/我应该看看的文件/如何做到的想法?
谢谢!
答案 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
)。
可以使用conj
,update-in
,assoc
等进行各种插入/拆分重排,因为哈希映射文字是常规的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}}