我试图在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,但我并不是真的喜欢那里使用的邻接表示。我知道我很讨厌,但我想知道是否没有其他可能解决这个问题。
如果有人可以帮我解决问题或摆脱我的幻想,那将会有所帮助......
提前致谢
答案 0 :(得分:2)
所以有几种不同的选择:
在Haskell中有很多方法可以编码图形。绝对最简单的方法是使用一个名为&#34;结结&#34;在树数据结构中创建圆度。例如,对此图进行编码:
.--.
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数据结构一样遍历数据结构,具有尾递归函数。要在循环时终止,在递归时将当前项添加到path
变量上,并且递归逻辑应该说的第一件事是| node
elem path = ...
来处理你想要的循环
另一方面,您的次要一致性问题已经引发了一些棘手的一致性问题。考虑一下这两者之间的区别:
-- 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]
所以这很糟糕,我唯一真正的答案是,&#34;通过转换为以下其中一个来正常化...&#34;。
无论如何,上面的技巧也是名称相互递归和 letrec ,基本上意味着在where
或let
子句中,你放在那里的所有定义可以&#34;看到彼此&#34;。它不是懒惰的财产;您也可以使用严格的语言制作上述数据结构 - 但功能性严格语言的语言设计理解这种相互递归的定义可能有点困难。 (使用非功能性语言,您只需根据需要创建指针。)
现在考虑一下如何使用我们上面的图表,并将其转换为您的代表。最简单的方法是通过一个包含Array
:
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
也就是说,我们使用一个映射将边键映射到它们的子集;我们将其初始化为映射到空集的顶点列表(以便我们可以在图中使用断开的单个节点),然后通过将值插入到键的集合中来遍历边缘,如果我们不这样做则创建该集合。 ;看到钥匙。