Data structures 如何在Haskell中实现半边数据结构?

Data structures 如何在Haskell中实现半边数据结构?,data-structures,haskell,Data Structures,Haskell,有关数据结构的说明,请参见 半边数据结构涉及循环 可以用Haskell这样的函数式语言实现吗 可变引用(STRef)是一种可行的方法吗 谢谢很明显,问题在于半边引用下一个半边和另一个半边(其他引用没有问题)。您可以“打破循环”,例如,不直接引用其他半边,而只引用一个ID(例如简单整数)。为了按ID查找半边,可以将它们存储在Data.Map中。当然,这种方法需要一些簿记,以避免一个大混乱,但这是我能想到的最简单的方法 愚蠢的我,我认为我不够懒。上面的解决方案适用于严格的函数式语言,但对

有关数据结构的说明,请参见

半边数据结构涉及循环

  • 可以用Haskell这样的函数式语言实现吗
  • 可变引用(STRef)是一种可行的方法吗
谢谢

很明显,问题在于半边引用下一个半边和另一个半边(其他引用没有问题)。您可以“打破循环”,例如,不直接引用其他半边,而只引用一个ID(例如简单整数)。为了按ID查找半边,可以将它们存储在Data.Map中。当然,这种方法需要一些簿记,以避免一个大混乱,但这是我能想到的最简单的方法


愚蠢的我,我认为我不够懒。上面的解决方案适用于严格的函数式语言,但对Haskell来说是不必要的。

如果所讨论的任务允许您构建半边结构一次,然后多次查询,那么,正如注释和其他答案中所指出的,惰性绑定已知方法是一种方法


然而,若您想要更新您的结构,那个么纯功能接口可能会被证明是很麻烦的。此外,还需要考虑更新函数的O(..)要求。最终可能会发现,您需要可变的内部表示(可能是在顶部使用纯API)。

我遇到了一个多态性的有用应用程序来解决这类问题。您通常需要一个用于序列化的静态非无限版本,以及一个用于内部表示的非无限版本

如果生成一个多态版本,则可以使用记录语法更新该特定值:

data Foo edge_type_t = Depot {
   edge_type :: edge_type_t,
   idxI, idxE, idxF, idxL :: !Int
} deriving (Show, Read)

loadFoo edgetypes d = d { edge_type = edgetypes ! edge_type d }
unloadFoo d = d { edge_type = edgetype_id $ edge_type d }

但是有一个主要的警告:因为Haskell必须递归地理解类型的属性:(

为了有效地构造半边数据结构,需要一个用于HE\u vert的加速结构(我们称之为HE\u vert\u acc…但实际上,您可以直接在HE\u vert中执行此操作),该结构保存指向该HE\u vert的所有HE\u边。否则,在尝试定义“HE_edge*pair”(即方向相反的相邻半边),例如通过蛮力比较

因此,使用打结方法可以很容易地为单个面创建半边数据结构,因为(可能)有无论如何,没有对。但是如果你增加加速结构的复杂性来有效地决定这些对,那么它就变得有点困难,因为你需要在不同的面上更新相同的HE\u vert\u acc,然后更新HE\u边以包含一个有效的对。这实际上是多个步骤。你将如何粘合通过打结将它们结合在一起要比构建一个循环的双链表复杂得多,而且并不明显

正因为如此,我才不会为“如何用惯用的haskell构造这个数据结构”这个问题操心太多。 我认为在保持API的功能性的同时,在这里使用更多的命令式方法是合理的。我可能会选择数组和状态单子

我不是说结婚是不可能的,但我还没有看到这样的实现。在我看来,这不是一个容易的问题

编辑:所以我不能放弃并实现它,假设输入是一个.obj网格文件

我的方法基于这里描述的方法,但是Andrew Bromage的方法,他解释了在编译时不知道结的情况下为DFA打结

不幸的是,半边数据结构甚至更复杂,因为它实际上由3个数据结构组成

所以我从我真正想要的开始:

data HeVert a = HeVert {
    vcoord  :: a        -- the coordinates of the vertex
  , emedge  :: HeEdge a -- one of the half-edges emanating from the vertex
}

data HeFace a = HeFace {
  bordedge :: HeEdge a -- one of the half-edges bordering the face
}

data HeEdge a = HeEdge {
    startvert :: HeVert a          -- start-vertex of the half-edge
  , oppedge   :: Maybe (HeEdge a)  -- oppositely oriented adjacent half-edge
  , edgeface  :: HeFace a          -- face the half-edge borders
  , nextedge  :: HeEdge a          -- next half-edge around the face
}
问题是,在高效构建时,我们会遇到多个问题,因此对于所有这些数据结构,我们将使用“间接”数据结构,它基本上只保存.obj网格文件提供的简单信息

所以我想到了这个:

data IndirectHeEdge = IndirectHeEdge {
    edgeindex  :: Int  -- edge index
  , svindex    :: Int  -- index of start-vertice
  , nvindex    :: Int  -- index of next-vertice
  , indexf     :: Int  -- index of face
  , offsetedge :: Int  -- offset to get the next edge
}

data IndirectHeVert = IndirectHeVert {
    emedgeindex  :: Int    -- emanating edge index (starts at 1)
  , edgelist     :: [Int]  -- index of edge that points to this vertice
}

data IndirectHeFace =
  IndirectHeFace (Int, [Int]) -- (faceIndex, [verticeindex])
有些事情可能不是直观的,可以做得更好,例如“offsetedge”的事情。 看看我怎么没有把实际的顶点保存到任何地方。这只是很多模拟C指针的索引。 我们需要“边缘主义者”,以便在以后有效地找到方向相反的半边缘

我不会详细介绍如何填充这些间接数据结构,因为这实际上是特定于.obj文件格式的。我只给出一个有关如何转换的示例

假设我们有以下网格文件:

v 50.0 50.0
v 250.0 50.0
v 50.0 250.0
v 250.0 250.0
v 50.0 500.0
v 250.0 500.0
f 1 2 4 3
f 3 4 6 5
间接面现在将如下所示:

[IndirectHeFace (0,[1,2,4,3]),IndirectHeFace (1,[3,4,6,5])]
间接边:

[IndirectHeEdge {edgeindex = 0, svindex = 1, nvindex = 2, indexf = 0, offsetedge = 1},
IndirectHeEdge {1, 2, 4, 0, 1},
IndirectHeEdge {2, 4, 3, 0, 1},
IndirectHeEdge {3, 3, 1, 0, -3},
IndirectHeEdge {0, 3, 4, 1, 1},
IndirectHeEdge {1, 4, 6, 1, 1},
IndirectHeEdge {2, 6, 5, 1, 1},
IndirectHeEdge {3, 5, 3, 1, -3}]
和间接顶点:

[(1,IndirectHeVert {emedgeindex = 0, edgelist = [3]}),
(2,IndirectHeVert {1, [0]}),
(3,IndirectHeVert {4, [7,2]}),
(4,IndirectHeVert {5, [4,1]}),
(5,IndirectHeVert {7, [6]}),
(6,IndirectHeVert {6, [5]})]
现在真正有趣的部分是我们如何将这些间接数据结构转换为我们在一开始定义的“直接”数据结构。这有点棘手,但基本上只是索引查找,并且由于惰性而起作用

以下是伪代码(实际实现不仅使用列表,而且为了使函数安全,还增加了额外的开销):

indirectToDirect::[a]--解析的顶点,例如2d点(双精度,双精度)
->[间接边缘]
->[间接脸]
->[IndirectHeVert]
->注意
间接指向点边面顶点
=此边缘(头部边缘)
哪里
此边
=HeEdge(thisVert(顶点!!svindex边)$svindex边)
(thisOppEdge(svindex edge)$indexf edge)
(thisFace$faces!!indexf edge)
(此边$边!!(边索引边+偏移边边))
thisFace=HeFace$thisEdge(边!!(head.snd$face))
此垂直坐标
=HeVert(点!!(坐标索引-1))
(thisEdge$points!!(emedgeindex vertice-1))
此OppEdge startverticeindex面索引
=此边
(h)
indirectToDirect :: [a]   -- parsed vertices, e.g. 2d points (Double, Double)
                 -> [IndirectHeEdge]
                 -> [IndirectHeFace]
                 -> [IndirectHeVert]
                 -> HeEdge a
indirectToDirect points edges faces vertices
  = thisEdge (head edges)
  where
    thisEdge edge
      = HeEdge (thisVert (vertices !! svindex edge) $ svindex edge)
               (thisOppEdge (svindex edge) $ indexf edge)
               (thisFace $ faces !! indexf edge)
               (thisEdge $ edges !! (edgeindex edge + offsetedge edge))
    thisFace face = HeFace $ thisEdge (edges !! (head . snd $ face))
    thisVert vertice coordindex
      = HeVert (points !! (coordindex - 1))
               (thisEdge $ points !! (emedgeindex vertice - 1))
    thisOppEdge startverticeindex faceindex
      = thisEdge
        <$>
        (headMay
          . filter ((/=) faceindex . indexf)
          . fmap (edges !!)
          . edgelist         -- getter
          $ vertices !! startverticeindex)