Haskell中的重用列表函数

时间:2017-06-22 10:09:58

标签: haskell recursion

我目前正在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"

我只需要提示如何进一步处理以及如何再次访问已找到的链接。我应该使用折叠吗?

2 个答案:

答案 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 = URLtraverse吗?

答案 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