目录树的广度优先遍历不是懒惰的

编程入门 行业动态 更新时间:2024-10-11 23:16:59
本文介绍了目录树的广度优先遍历不是懒惰的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我试图遍历目录树。幼稚的深度优先遍历似乎不会以懒惰的方式产生数据并且耗尽内存。我接下来尝试了第一种宽度的方法,它显示了同样的问题 - 它使用所有可用的内存然后崩溃。

我的代码是:

getFilePathBreadtFirst :: FilePath - > IO [FilePath] getFilePathBreadtFirst fp = do fileinfo< - getInfo fp res :: [FilePath]< - if isReadableDirectory fileinfo then do children< ; - getChildren fp lower < - mapM getFilePathBreadtFirst children return(children ++ concat lower) else return [fp] - 应该只返回文件吗? 返回资源 getChildren :: FilePath - > IO [FilePath] getChildren path = do 名称< - getUsefulContents路径 let namesfull = map(路径< />)名称返回namesfull testBF fn = do - 崩溃/ home / frank,不去掉 fps< - getFilePathBreadtFirst fn putStrLn $ unlines fps

我认为所有的代码都是线性的或者是尾递归的,我期望文件名列表立即开始,但实际上并不是。我的代码中的错误和我的想法在哪里?我在哪里失去了懒惰的评价?

解决方案

我会用三个独立的技巧来解决你的问题。 b

  • 特技1 :使用管道
  • 遍历。
  • 技巧3 :使用 MaybeT 变换器来避免手动递归, 。

    以下代码将这三种技巧结合在一个monad变量堆栈中。

    import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Maybe import Control.Monad.State.Lazy import Control.Pipe import Data.Sequence import System.FilePath.Posix import System.Directory loop ::(Monad m)=> ; MaybeT m a - > m() loop = liftM(maybe()id)。 runMaybeT。永远 quit ::(Monad m)=> MaybeT m a quit = mzero getUsefulContents :: FilePath - > IO [FilePath] getUsefulContents路径 = fmap(filter(`notElem` [。,..]))$ getDirectoryContents路径 允许:: FilePath - > IO Bool 允许的文件 = fmap(\ p - >可读的p&&&搜索p)$ getPermissions文件 traverseTree :: FilePath - >生产者FilePath IO() traverseTree path =(``evalStateT` empty)$ loop $ do - 过去这一点的所有代码都使用以下monad变量堆栈: - MaybeT(StateT Seq FilePath)(Producer FilePath IO))() let liftState = lift liftPipe = lift。提升 liftIO =提升。电梯 。 lift liftState $ modify(|>路径)永远$ do x< - liftState $获取viewl case x of EmptyL - >退出文件:< s - > do liftState $ put s liftPipe $ yield文件p< - liftIO $ doesDirectoryExist文件当p $ do 名称< - liftIO $ getUsefulContents文件 - allowedNames< - filterM允许的名称 let namesfull = map(路径< />)名称 liftState $ forM_ namesfull $ \\\ame - >修改(|>名称)

    这将创建一个可以消费的广度优先文件名与树遍历同时进行。您可以使用以下值来使用这些值:

    printer ::(Show a)=>消费者a IO r printer = forever $ do a< - 等待 lift $打印 >>> runPipe $ printer< +<遍历树路径<在遍历树时打印文件名称>

    您甚至可以选择不要求所有的值:

    - 仅需'n'元素以'::(Monad m)=> Int - > Pipe a a m() take'n = replicateM_ n $ do a< - await yield a >> runPipe $ printer< +<采取'3 +<遍历树路径<仅打印三个文件>

    更重要的是,最后一个例子只会根据需要遍历树来生成三个文件,那么它会停止。这可以防止浪费地遍历整个树,当你想要的只有3个结果!

    了解更多关于管道库请参阅管道教程 at Control.Pipes.Tutorial 。

    要详细了解循环技巧,请阅读博客文章。

    我无法为广度优先遍历的队列技巧找到一个好的链接,但我知道它在某处。如果其他人知道这个好链接,只需编辑我的答案来添加它。

    I try to traverse the directory tree. A naive depth-first traversal seems not to produce the data in a lazy fashion and runs out of memory. I next tried a breadth first approach, which shows the same problem - it uses all the memory available and then crashes.

    the code I have is:

    getFilePathBreadtFirst :: FilePath -> IO [FilePath] getFilePathBreadtFirst fp = do fileinfo <- getInfo fp res :: [FilePath] <- if isReadableDirectory fileinfo then do children <- getChildren fp lower <- mapM getFilePathBreadtFirst children return (children ++ concat lower) else return [fp] -- should only return the files? return res getChildren :: FilePath -> IO [FilePath] getChildren path = do names <- getUsefulContents path let namesfull = map (path </>) names return namesfull testBF fn = do -- crashes for /home/frank, does not go to swap fps <- getFilePathBreadtFirst fn putStrLn $ unlines fps

    I think all the code is either linear or tail recursive, and I would expect that the listing of filenames starts immediately, but in fact it does not. Where is the error in my code and my thinking? Where have I lost lazy evaluation?

    解决方案

    I will use three separate tricks to solve your question.

    • Trick 1: Use the pipes library to stream file names concurrent with traversing the tree.
    • Trick 2: Use the StateT (Seq FilePath) transformer to achieve a breadth-first traversal.
    • Trick 3: Use the MaybeT transformer to avoid manual recursion when writing the loop and exit.

    The following code combines these three tricks in one monad transformer stack.

    import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Maybe import Control.Monad.State.Lazy import Control.Pipe import Data.Sequence import System.FilePath.Posix import System.Directory loop :: (Monad m) => MaybeT m a -> m () loop = liftM (maybe () id) . runMaybeT . forever quit :: (Monad m) => MaybeT m a quit = mzero getUsefulContents :: FilePath -> IO [FilePath] getUsefulContents path = fmap (filter (`notElem` [".", ".."])) $ getDirectoryContents path permissible :: FilePath -> IO Bool permissible file = fmap (\p -> readable p && searchable p) $ getPermissions file traverseTree :: FilePath -> Producer FilePath IO () traverseTree path = (`evalStateT` empty) $ loop $ do -- All code past this point uses the following monad transformer stack: -- MaybeT (StateT (Seq FilePath) (Producer FilePath IO)) () let liftState = lift liftPipe = lift . lift liftIO = lift . lift . lift liftState $ modify (|> path) forever $ do x <- liftState $ gets viewl case x of EmptyL -> quit file :< s -> do liftState $ put s liftPipe $ yield file p <- liftIO $ doesDirectoryExist file when p $ do names <- liftIO $ getUsefulContents file -- allowedNames <- filterM permissible names let namesfull = map (path </>) names liftState $ forM_ namesfull $ \name -> modify (|> name)

    This creates a generator of breadth-first file names that can be consumed concurrent with the tree traversal. You consume the values using:

    printer :: (Show a) => Consumer a IO r printer = forever $ do a <- await lift $ print a >>> runPipe $ printer <+< traverseTree path <Prints file names as it traverses the tree>

    You can even choose to not demand all the values:

    -- Demand only 'n' elements take' :: (Monad m) => Int -> Pipe a a m () take' n = replicateM_ n $ do a <- await yield a >> runPipe $ printer <+< take' 3 <+< traverseTree path <Prints only three files>

    More importantly, that last example will only traverse the tree as much as necessary to generate the three files and then it will stop. This prevents wastefully traversing the entire tree when all you wanted was 3 results!

    To learn more about the pipes library trick, consult the pipes tutorial at Control.Pipes.Tutorial.

    To learn more about the loop trick, read this blog post.

    I couldn't find a good link for the queue trick for breadth first traversal, but I know it's out there somewhere. If somebody else knows a good link for this, just edit my answer to add it.

更多推荐

目录树的广度优先遍历不是懒惰的

本文发布于:2023-11-05 02:33:46,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:遍历   广度   懒惰   目录

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!