我正在努力弄清楚如何在Haskell中读取(以及如何表示)图形。
文件的输入类似于
NODES 3
EDGE 1 2
EDGE 1 3
EDGE 2 3
我已经想出如何使用以下方法从文件中获取各行输入:
loadFile :: String -> IO [[String]]
loadFile filename = do
contents <- readFile filename
return $ map words $ lines contents
这给出了输出:
loadFile "input.txt"
[["NODES","3"],["EDGE","1","2"],["EDGE","1","3"],["EDGE","2","3"]]
我真正需要弄清楚的是如何将这个图形数据表示为图形。 我想把它设置为边缘列表:
type Edge = (Int,Int)
type Graph = [Edge]
但后来我不确定如何开始实现我需要的功能,例如addNode,addEdge,getNodes,getEdges。
任何帮助或指向我正确的方向都会很棒!注意:我不能使用任何已经开发的图形模块。
所以,对于tl; dr版本:
答案 0 :(得分:7)
这里有很多有趣的问题。让我攻击他们。
您正在阅读数据,这对于面向行的语言来说非常合适。稍后,您会看到Data.ByteString
和Data.Text
替换String
以提高效率。您还会看到Parsec
进行解析。但是,他们可以等待。及时重访。
您的图表表示很好。邻接列表是一种常见且有用的表示。
现在,真正的伎俩就在这里。我们来看看addNode
和addEdge
。每个都是一个有点挑战性的函数,用纯函数式语言生成,因为他们想修改一个图...但我们没有状态。
修改无状态最重要的方法是变异。因此,您正在寻找的功能是
addNode :: Node -> Graph -> Graph
返回的Graph
与输入Graph
相同,只有一条边。你应该立即注意到这里有什么问题---邻接列表假设没有孤立节点。我们不能只在图表中添加一个节点。
有两种解决方案。一,我们可以将节点“链接”到图形中(伪装真的是addEdge
)或两个我们可以扩展图形表示以包括孤立节点。我们来做(2)。
data Graph = Graph [Edge] [Int] -- orphans
现在让我们实现添加边缘。假设您可以有重复的边,向邻接列表添加边很容易,只需附加
addEdge0 :: Edge -> Graph -> Graph
addEdge0 e (Graph adj orph) = Graph (e:adj) orph
但这还不够好 - 我们希望我们的孤儿列表只包含真正的孤立节点。我们会过滤它。
addEdge :: Edge -> Graph -> Graph
addEdge (n1,n2) (Graph adj orph) =
Graph ((n1,n2):adj) (filter (/=n1) . filter (/=n2) $ orph)
getEdges
是微不足道的,因为我们已经存储了边缘列表
getEdges :: Graph -> [Edge]
getEdges (Graph edges _) = edges
getNodes
只需要将邻接列表中的所有节点附加到孤立列表。我们可以使用Data.List.nub
来获取唯一的节点。
getNodes :: Graph -> [Int]
getNotes (Graph adj orph) = nub (orph ++ adjNodes adj) where
adjNodes [] = []
adjNodes ((n1,n2):rest) = n1 : n2 : adjNodes rest
希望这些能为您提供一些如何用功能语言思考的指示。你会花些时间去深入了解它们是如何工作的,但我在这里介绍了大量有趣的概念。
此处的后续步骤可能包括尝试使用State
monad重新捕获命令状态修改并将这些Graph
链接在一起修改函数。