我會用三個獨立的技巧來解決你的問題。
- 招數一:使用
pipes
庫併發流與遍歷樹的文件名。
- 把戲2:使用
StateT (Seq FilePath)
變壓器來實現寬度優先遍歷。
- 技巧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
庫技巧的更多信息,請參閱pipes tutorial,網址Control.Pipes.Tutorial
。
要了解有關循環技巧的更多信息,請閱讀blog post。
我無法找到廣泛優先遍歷的隊列技巧的好鏈接,但我知道它在那裏。如果其他人知道這個好鏈接,只需編輯我的答案來添加它。
謝謝你的代碼。理解管道很有幫助。我正在閱讀有關管道並正在計劃使用它,但預計我應該首先有一個簡單的懶惰解決方案,只有樹遍歷。 我試了一下,它的工作原理,但它沒有遞歸樹,我不明白它會在你的代碼遞歸。 缺少的代碼正在過濾掉「。」。和「..」 getUsefulContents path = do names < - getDirectoryContents path return(filter('notElem' [「。」,「..」])names) – user855443
關於更深的檢查我看到了(隱藏)在liftstate的最後一行中遞歸,其中新文件名被添加到「todo」列表中。 我沒有看到這個,因爲代碼不會爲添加的文件生成完整的文件路徑。路徑的值是原始起始值,並且不會每次都設置爲當前文件名 - >用文件替換路徑,然後運行。 要完全工作,必須檢查目錄上的權限,我使用 getInfo :: FilePath - > IO信息 我從現實世界中採取了哈斯克爾第9章。 – user855443
它遇到困難,當它遇到鏈接和我必須添加一個測試來濾除鏈接。 它的工作原理和使用我所有的4核心!仍然存在內存泄漏,因爲使用量增長非常緩慢,直到耗盡內存。你能看到哪裏? 你的幫助是非常感謝,正是我需要有一個很好的實例如何穿越樹時使用管道! – user855443