我将使用三个不同的技巧来解决您的问题。
- 技巧 1:使用
pipes
库在遍历树的同时流式传输文件名。
- 技巧2:使用
StateT (Seq FilePath)
transformer实现广度优先遍历。
- 技巧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 . 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)
这将创建一个广度优先文件名生成器,可以与树遍历同时使用。您使用以下方式使用这些值:
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>
您甚至可以选择不要求所有值:
-- 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>
更重要的是,最后一个示例只会尽可能多地遍历树以生成三个文件,然后它将停止。当您想要的只是 3 个结果时,这可以防止浪费地遍历整个树!
要了解有关pipes
库技巧的更多信息,请参阅管道教程。Control.Pipes.Tutorial
要了解有关循环技巧的更多信息,请阅读此博客文章。
我找不到用于广度优先遍历的队列技巧的良好链接,但我知道它就在某个地方。如果其他人知道一个很好的链接,只需编辑我的答案以添加它。