我目前正在Haskell开展这个项目,在那里我分析一个网站并尝试查找属于这个网站的所有链接(href)。我已经能够提取主站点的所有链接,但我正在努力与递归,因为我想按照我已经找到的链接并再次执行相同的过程。
这就是我已经拥有的:
parseHtml = fmap LB.unpack . simpleHttp
filterFunc x y = -- damn long line with a lot of filters
main :: IO()
main = do
let site = "https://stackoverflow.com/"
url <- parseHtml site
let links = filterFunc site url
mapM_ print $ take 5 $ links
到目前为止这是我的输出:
"https://stackoverflow.com/company/about"
"https://stackoverflow.com/company/work-here"
"https://stackoverflow.com/help"
"https://stackoverflow.com/jobs/directory/developer-jobs"
"https://stackoverflow.com/questions/44691577/stream-versus-iterators-in-set"
我只需要提示如何进一步处理以及如何再次访问已找到的链接。我应该使用折叠吗?
答案 0 :(得分:1)
链接查找本质上是一个图遍历问题,由于功能纯度,在Haskell中可能很棘手:通过使用外部历史表很难明确地将节点(链接)标记为是否访问过。< / p>
您的典型遍历算法可能如下所示:
function traverse(current_node) {
if (current_node.is_visited) {
return some_data;
} else {
current_node.is_visisted = true; // Hard in Haskell!
accumulated_data = ...;
for (child in current_node.children()) {
accumulated_data += traverse(child); // Recursion happens here.
}
return accumulated_data;
}
}
由于没有一种简单,直接的方式将节点标记为已访问,我们可以尝试其他解决方案。例如,我们可能会考虑某种类型的东西:
traverse :: ([URL], Data) -> URL -> ([URL], Data)
traverse (history, datum) current = let ... in ([new_history], accumulated_data)
这里的想法如下:我们保留了我们访问过的URL
的明确列表。这允许我们从当前节点(URL
)快速返回,如果它出现在我们的历史列表中(可能是Set
用于优化?:))。在这种情况下,使用traverse
对子节点的每次后续调用都将获得new_history
列表,从而有效地跟踪已访问和未访问URL
的列表。
实现此目的的一种可能方法是使用折叠函数,例如foldl
:
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
此处类型t a
可能是[URL]
,表示当前链接的子项,我们的traverse
函数方便地具有类型签名(b -> a -> b)
,其中{{1} }和type b = ([URL], Data)
。
你能从这里拿出来并弄清楚如何结合type a = URL
和traverse
吗?
答案 1 :(得分:0)
只需将链接访问逻辑移动到一个单独的函数中,该函数将链接作为参数,然后按照您的直觉递归链接。
根据您最终要对链接执行的操作,您可以使用您的功能简单地折叠链接。
例如,稍微修改一下代码:
parseHtml = fmap LB.unpack . simpleHttp
filterFunc x y = -- damn long line with a lot of filters
visitLink :: String -> IO ()
visitLink site = do
url <- parseHtml site
let links = filterFunc site url
mapM_ print $ take 5 $ links -- or whatever you want to do on your links
mapM_ visitLink links -- the recursive call
main :: IO()
main = visitLinks "https://stackoverflow.com/"
如果,而不是像你一样打印链接,你宁愿返回它们,调整visitLink
函数的返回类型(例如String -> IO [String]
并更改visitLink
中的最后一行适当地(例如fmap join $ mapM visitLinks links
)。
正如在另一个答案中提到的,请记住,使用这么简单的代码,您可以无限次访问同一个链接。考虑将您访问的链接存储在合适的数据结构(例如集合)中,然后传递给visitLink
。