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