图形构建过程中的连续传递风格(CPS)

时间:2011-09-15 09:41:43

标签: haskell graph continuations continuation-passing

我正在为细分曲面创建一个库。为了表示网格拓扑,我使用了一种分裂顶点板条数据结构(参见左侧的图表)。

Mesh data structure

在构建网格时,也可以看作图形,它创建的节点应指向另一个尚不存在的节点(参见右侧图表 - 虚线箭头表示未来链接)。经典的解决方案是创建一个带空指针的节点,然后在创建另一个节点时更新它。因为我正在研究Haskell :)而且我不想去代码的黑暗面(杂质)我想知道是否有可能在不更新数据的情况下构造网格(图形)。我想CPS(延续传递风格)可以完成这项任务,但我无法找到方法。

这只是一个梦吗?

更新

让我稍微澄清一下我的问题。我正在寻找一种方法来创建具有直接链接(指针)的节点,并通过直接链接我的意思是没有中间表或映射。只是一个简单的数据定义:

data Mesh = Edge Vertex Mesh Mesh Vertex | Ground

如果我没有错,并且如果它是可行的,CPS将允许有效创建(没有节点更新)和图的有效横向(没有在地图上查找)。另一方面,图形将变得完全不可变,即,需要在整个图形中传播单个变化,例如,改变列表的尾部。

我错了吗?如果不是,该怎么做?

3 个答案:

答案 0 :(得分:4)

您需要的是一种称为tying the knot的技术。它利用懒惰的评估来完成工作。不需要CPS。

假设您可以通过某个唯一ID(字符串,整数或其他)来标识每个节点。还假设在创建节点时,您已经知道它指向的所有节点的ID,无论它们是否已经创建。然后你可以使用这种技术。

通过图表创建功能将nodes :: Data.Map NodeID Node字符串(为了方便起见,使用状态monad)。创建节点时,将其添加到地图中。当您创建应指向名为x的节点的边时,您使用fromMaybe $ lookup nodes x名为x的节点是否已创建,或将来是否会创建并不重要。只要 在某个时刻创建,就会被设置。它只会在您需要时从地图中获取。

这是我用来从文字描述中创建图表的方式。也许还有其他更好的方法。

如果在创建节点时,您不知道节点将指向的所有节点的ID,则需要稍微修改此技术,例如:将地图从节点ID传递到其邻居列表,并逐步构建每个列表。

在构建图表之前,您应该小心并避免评估惰性值。

答案 1 :(得分:2)

它不使用CPS ......但我正在为Haskell开发一个平面图库,使用与上述相似的方案。通过指定现有边缘在其之前或之后来添加边缘。

实际的图形实现已经完成,剩下的就是让二进制序列化工作和执行(使用PLANAR_CODE作为初学者,也可能是Graph6和Sparse6)以及其他一些额外的东西。

目前你使用单独的函数获得双重图形(这似乎也是你所绘制的图形),尽管我正在考虑每次添加边时计算双重(假设连接图形)。

代码可以从darcs get http://code.haskell.org/~ivanm/planar-graph/获得;示例用法(我正在为此开发此库的用途)位于http://code.haskell.org/~ivanm/dangd/


取自Haddock文档作为使用示例:

例如,让g参考下图(其中 n1等都是标签和变量名称:

     ====                    ====
    ( n1 )                  ( n2 )
     ====                    ====





                             ====
                            ( n3 )
                             ====

我们可以在n1n2之间添加边缘(使用 Anywhere 作为 EdgePos ,因为两个节点上当前没有边缘):

 ((e1,e2),g') = addEdge n1 Anywhere n2 Anywhere "e1" "e2" g

这将产生以下图表:

                  e2
     ====  <---------------  ====
    ( n1 )                  ( n2 )
     ====  --------------->  ====
                  e1




                             ====
                            ( n3 )
                             ====

如果我们想在n2n3之间添加边,我们有三个 n2上的位置选项:

  • 使用Anywhere:因为只有一个其他边缘,所以没有 第二条边缘的嵌入方面的差异。

  • 放置新边BeforeEdge e2(顺时针绕n2移动。)

  • 放置新边AfterEdge e2(顺时针绕n2移动。)

由于n2目前只有一条边,所有三个 EdgePos 值 会产生相同的图形,所以我们可以任意选择一个:

 ((e3,e4),g'') = addEdge n2 (BeforeEdge e2) n3 Anywhere "e3" "e4" g'

但是,如果需要更多边缘,必须注意 EdgePos 使用了价值。结果图是:

                  e2
     ====  <---------------  ====
    ( n1 )                  ( n2 )
     ====  --------------->  ====
                  e1         |  ^
                             |  |
                          e3 |  | e4
                             |  |
                             v  |
                             ====
                            ( n3 )
                             ====

相同的图表(直到实际的 Edge 值;因此它不会满足 ==)本来可以获得:

 ((e4,e3), g'') = addEdge n3 Anywhere n2 (BeforeEdge e2) "e4" "e3" g'

答案 2 :(得分:0)

似乎您不需要将链接存储到Edge内的NextA和NextB边缘。由于这些是可以通过从当前Edge遍历计算的东西,为什么不编写一个带有Edge并返回其NextA / NextB边缘的函数,该边缘与Edge的A和B部分的顺时针方向相同。