3 가지 별도의 트릭을 사용하여 질문을 해결하겠습니다.
- 트릭 1는 : 트리를 통과 동시 파일 이름을 스트리밍 할
pipes
라이브러리를 사용합니다.
- 트릭 2 : 가로 너비 우선 탐색을 수행하려면
StateT (Seq FilePath)
변환기를 사용하십시오.
- 트릭 3 : 루프를 작성하고 종료 할 때 수동 재귀를 피하려면
MaybeT
변환기를 사용하십시오.
다음 코드는 이러한 세 가지 트릭을 하나의 모나드 변환기 스택에 결합합니다.
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을 읽어보십시오.
너비가 첫 번째 탐색을위한 대기열 트릭에 대한 좋은 링크를 찾을 수 없지만 어딘가에 있다는 것을 알고 있습니다. 다른 사람이이 링크를 잘 알고 있다면 내 대답을 편집하여 추가하십시오.
코드 주셔서 감사합니다. 파이프를 이해하는 데 큰 도움이됩니다. 나는 도관에 대해 읽고 그것을 사용할 계획 이었지만 먼저 트리 순회를위한 간단한 게으른 해결책이 있어야한다고 생각했습니다. 나는 그것을 시험해 보았지만 작동하지만 나무 아래로 재귀하지 않고 코드에서 어디에서 재귀하는지 이해하지 못한다. 누락 된 코드가 필터링됩니다. " 및 ".."dirs의 목록에서 getUsefulContents 경로 = 할 이름 <- getDirectoryContents 경로 반환 (필터 ('notElem' [ ".", ".."]) 이름) – user855443
깊은 검사에 나는 (숨겨진) 재귀를 사용하여 마지막 줄에 새 파일 이름이 "todo"목록에 추가되는 liftstate가있는 재귀. 코드가 추가 된 파일의 전체 파일 경로를 생성하지 않기 때문에 나는 이것을 보지 못했습니다. path의 값은 원래의 시작 값이며 매번 현재 파일 이름으로 설정되지 않습니다. -> path를 file로 바꾸면 작동합니다. 완전히 작동하려면 디렉토리에 대한 사용 권한을 확인해야합니다. getInfo :: FilePath -> IO Info 실제 세계에서 haskell 9 장을 가져 왔습니다. – user855443
링크가 만났을 때 어려움에 처하게됩니다. 링크를 걸러 내기위한 테스트를 추가해야합니다. 그것은 내 모든 4 코어를 사용하고 작동합니다! 메모리가 부족할 때까지 사용량이 매우 천천히 늘어남에 따라 메모리 누수가 여전히 발생합니다. 어디서 볼 수 있니? 당신의 도움이 많이 감사합니다, 그것은 정확하게 나무를 횡단 할 때 파이프를 사용하는 방법에 대한 좋은 실용적인 예를 가질 필요가있었습니다! – user855443