haskell – 如何确保图形中的正确边缘

我试图在
Haskell中为图形创建数据类型,如下所示:

type Vertex a = a
type Edge a = (Vertex a, Vertex a)
data Graph a = Combine [Vertex a] [Edge a]

这是一个适用于我想做的表示,但我意识到可能存在不在顶点列表中的顶点的边.

我的问题是,是否有可能确保每个边只包含顶点列表中的顶点?

我已经考虑了很多,但到目前为止我得到的最好的想法是一个函数,它将一个新的图形与边缘列表中所有缺少的顶点添加到顶点列表中.就像是:

fix_graph :: Graph a -> Graph a
fix_graph (Combine v e) = Combine (removeDuplicates (v ++ [x | (x,_) <- e] ++ [y | (_,y) <- e])) e

removeDuplicates :: [t] -> [t]
...

因为这个想法并没有真正满足我(也因为我没有花时间来很好地实现它),我想知道是否有可能有一个数据构造函数来添加不在顶点的边缘的顶点 – 立即列出.

我已经阅读了答案here,但我并不是真的喜欢那里使用的邻接表示.我知道我很讨厌,但我想知道是否没有其他可能解决这个问题.

如果有人可以帮我解决问题或摆脱我的幻想,那将会有所帮助……

提前致谢

最佳答案 所以有几个不同的选择:

绑结

有很多方法可以在Haskell中编码图形.最简单的方法是使用一个名为“绑结”的过程在树数据结构中创建圆度.例如,对此图进行编码:

                  .--.
  A -- B -- C -- D -./
  |    |    |    |
  E -- F    G -- H
  |              |
  +--------------+

您只需编写一个节点作为其名称和子项列表:

data Node = Node String [Node]
instance Eq Node where
    Node a _ == Node b _ = a == b

my_graph = [a, b, c, d, e, f, g, h] where
    a = Node "A" [b, e]
    b = Node "B" [a, c, f]
    c = Node "C" [b, d, g]
    d = Node "D" [c, d, h]
    e = Node "E" [a, f, h]
    f = Node "F" [b, e]
    g = Node "G" [c, h]
    h = Node "H" [d, e, g]

这有很多方便:您现在可以像任何其他Haskell数据结构一样遍历数据结构,具有尾递归函数.要在循环上终止,在递归时将当前项添加到路径变量上,并且递归逻辑应该说的第一件事是,| nodeelempath = …来处理你想要的循环.

另一方面,您的次要一致性问题已经引发了一些棘手的一致性问题.考虑一下这两者之间的区别:

-- A has one child, which is itself; B has one child, which is A.
let a = Node "A" [a]; b = Node "B" [a] in [a, b]

-- this looks almost the same but if you descend far enough on B you find an
-- impostor node with the wrong children.
let a = Node "A" [a]
    impostor = Node "A" [b]
    b = Node "B" [Node "A" [Node "A" [impostor]]] 
in [a, b]

所以这很糟糕,我唯一真正的答案就是“通过转换为下面的一个来规范化……”.

无论如何,上面的技巧也通过名称相互递归和letrec,并且基本上意味着在where或let子句中,你放在那里的所有定义都可以“看到彼此”.它不是懒惰的财产;您也可以使用严格的语言来创建上述数据结构 – 但是这种方式理解相互递归定义的功能严格语言的语言设计可能有点困难. (使用非功能性语言,您只需根据需要创建指针.)

明确的数字命理学

现在想想你如何使用我们上面的图表,并将其转换为你的表示.最简单的方法是通过一个包含数组的中间步骤:

import From.Above.Code (Node)
import Data.Array

type Graph = Array [Int]

graph :: [Node] -> Maybe Graph
graph nodes = fmap (array (1, length nodes)) . sequence $map format nodes where
    indices = zip nodes [1..]
    pair x y = (x, y)
    format node@(Node _ children) = do -- in the Maybe monad
        index <- lookup node indices
        index_list <- sequence $map (flip lookup indices) children
        return (index, index_list)

现在,这一致性问题要少得多,现在可以通过编程方式缓解这些问题.但是,如果要以编程方式使用State monad创建此类图形,并且希望暂时将数据结构保持在不一致状态,直到读取正确的节点,则这些一致性问题可以起作用.唯一的缺点是,当您将图形写入文件时,它看起来有点难以理解,因为数字不如字符串友好:

 array (1, 8) [
     (1, [2, 5]),
     (2, [1, 3, 6]),
     (3, [2, 4, 7]),
     (4, [3, 4, 8]),
     (5, [1, 6, 8]),
     (6, [2, 5]),
     (7, [3, 8]),
     (8, [4, 5, 7])]

您可以使用Map String [String]来解决此问题,以便进行权衡,从而获得O(log n).在任何情况下,你都应该学习这种表示:当你进行你提出的“完整性检查”时,你会想要转换为IntMap [Int]并返回.

一旦你得到了这些,事实证明你可以使用一个支持Array Int Node来创建一个递归的[Node],如上所述:

nodesFromArray arr = nodes where
    makeNode index children = Node (show index) [backingArray ! c | c <- children]
    backingArray = array (bounds arr) [(i, makeNode i c) | (i, c) <- assocs arr]
    nodes = map makeNode arr

边缘列表

一旦你有了上面的列表(Map.toList或Array.assocs),边缘列表变得非常容易:

edges_from_array = concatMap . uncurry (fmap . pair) . assocs

翻转方面稍微复杂一点,完成了你想要直接做的事情:

import Data.Map (Map)
import Data.Set (Set)
import qualified Data.Map as Map
import qualified Data.Set as Set

makeGraphMap vertices edges = add edges (Map.fromList $blankGraph vertices) where
     blankGraph verts = zip verts (repeat Set.empty)
     setInsert x Nothing = Just $Set.singleton x
     setInsert x (Just set) = Just $Set.insert x set
     add [] graphMap = fmap Set.toList graphMap
     add ((a, b) : es) graphMap = Map.alter (setInsert b) a verts

也就是说,我们使用地图来遍历边缘列表,该地图将键映射到他们的子集;我们将其初始化为映射到空集的顶点列表(以便我们可以在图中使用断开的单个节点),然后通过将值插入到键的集合中来遍历边缘,如果不这样做则创建该集合看到钥匙.

点赞