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

2024-01-06

我尝试遍历目录树。简单的深度优先遍历似乎不会以惰性方式生成数据,并且会耗尽内存。接下来,我尝试了广度优先方法,该方法显示了相同的问题 - 它使用了所有可用内存,然后崩溃。

我的代码是:

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

我认为所有代码要么是线性的,要么是尾递归的,我希望文件名列表立即开始,但事实上并非如此。我的代码和思路哪里出了问题?我在哪里失去了懒惰的评估?


我将使用三个单独的技巧来解决你的问题。

  • Trick 1: 使用pipes用于在遍历树的同时流式传输文件名的库。
  • Trick 2: 使用StateT (Seq FilePath)Transformer 来实现广度优先遍历。
  • Trick 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图书馆技巧,请参阅管道教程 http://hackage.haskell.org/packages/archive/pipes/2.3.0/doc/html/Control-Pipe-Tutorial.html at Control.Pipes.Tutorial.

要了解有关循环技巧的更多信息,请阅读此内容博客文章 http://www.haskellforall.com/2012/07/breaking-from-loop.html.

我找不到广度优先遍历的队列技巧的好链接,但我知道它就在那里。如果其他人知道这方面的好链接,只需编辑我的答案即可添加它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

目录树的广度优先遍历并不懒惰 的相关文章

  • 我应该使用什么递归方案来重复有效的操作,直到其结果符合某些标准?

    也就是说 我要问的是一个循环 effectful Int gt IO Int effectful n do putStrLn Effect show n return n condition 3 final Int gt IO final
  • 有什么方法可以在 do / while / let 块中打印出变量的类型吗?

    有没有办法打印出嵌套变量的推断类型ghci 考虑代码 let f g where g x Int x 那么 最好查询一下类型g e g t f g会打印出Int gt Int 您可以通过给出适当的错误类型注释并检查错误消息来诱骗此信息 Ma
  • 管道中缺少 ResourceT 实例

    我在尝试使用时遇到奇怪的错误ResourceT http hackage haskell org package conduit 1 0 9 1 docs Data Conduit html t 3aResourceT来自管道 1 0 9
  • 地图不是接受一个函数而列表返回一个列表吗?

    map2 List a gt b gt c gt a gt b gt c map2 List f map2 List f a as bs map f a bs map2 List f as bs 这是我的讲座中的一个示例 它尝试将二元函数应
  • 如何避免编写这种类型的 Haskell 样板代码

    我经常遇到这种情况 这很烦人 假设我有一个 sum 类型 它可以保存一个实例x或一堆其他无关的事情x data Foo x X x Y Int Z String other constructors not involving x 要声明
  • 类型级编程有哪些示例? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我不明白 类型级编程 是什么意思 也无法使用Google找到合适的解释 有人可以提供一个演示类型级编程的示例吗 范式的解释和 或定义将
  • Haskell 中实例声明的参数顺序切换

    我想进行实例声明 但自由类型变量不是最后一个变量 例如 我有一个类声明 class Poppable m where tryPop m a gt Maybe a m a 现在我想让 Q PSQ 优先级队列 成为 Poppable 的实例 具
  • 动态加载编译的 Haskell 模块 - GHC 7.6

    我正在尝试使用 GHC API 动态编译和加载 Haskell 模块 我知道 API 从一个版本到另一个版本波动很大 所以我专门谈论 GHC 7 6 我尝试在 MacOS 和 Linux 上运行相同的代码 在这两种情况下 插件模块都可以正常
  • Haskell 中的多态函数作为参数

    我有一个带有两个构造函数的 ADT 一个包裹着一个Double和一个包裹着Integer 我想创建一个函数 它采用一元函数Numtypeclass 并返回一个函数 该函数将该一元函数应用于我的 ADT 的内容 我试过这个 data X Y
  • 是否有适用于 Haskell 或 Scala 等函数式语言的 LL 解析器生成器?

    我注意到明显缺乏用函数式语言创建解析器的 LL 解析器 我一直在寻找但没有成功的理想发现是为 ANTLR 风格的 LL 语法生成 Haskell 解析器 语法的模小数重新格式化 并且令我惊讶的是 每个最后一个解析器生成器都具有函数我发现的语
  • haskell复制目录的方法是什么

    我发现自己用 Haskell 编写越来越多的脚本 但在某些情况下 我真的不确定如何 正确 地做到这一点 例如递归地复制目录 a la unixcp r 由于我主要使用 Linux 和 Mac OS 所以我通常会作弊 import Syste
  • `arr fst` 是如何自然变换的?

    I asked 这个问题 https stackoverflow com q 62733726 11143763不久以前 这是关于以下箭头定律 arr fst first f f arr fst Category k gt k b c gt
  • Haskell 中是否可以部分应用第 n 个参数?

    我很好奇是否可以写一个函数apply nth它接受一个函数 参数的数量以及该参数的值 然后返回一个新的 部分应用的函数 我的感觉是 由于类型系统的原因 这是不可能的 但我无法给出令人满意的答案 我也无法提出工作类型签名 如果语言的类型更加松
  • 为什么 Kleisli 不是 Monoid 的一个实例?

    如果您希望附加两个类型 a gt m b 的函数 以便只得到一个附加两个结果的相同类型的函数 您可以使用 Kleisli 来执行此操作 instance Monad m Monoid b gt Monoid Kleisli m a b wh
  • 无法让 wxHaskell 在 Mac 上从 ghci 工作

    我正在尝试跑步一个例子 http www haskell org haskellwiki WxHaskell Quick start Hello world in wxHaskell using EnableGUI function htt
  • Haskell 中的随机枢轴快速排序

    是否有可能在 Haskell 中实现快速排序 使用 RANDOM PIVOT 但仍然有一个简单的Ord a gt a gt a 签名 我开始了解 Monad 目前 我将 monad 解释为某种 命令模式 这对于 IO 非常有用 所以 我知道
  • Haskell 中的 Monad 和 Purity

    我的问题是 Haskell 中的 monad 是否真正保持了 Haskell 的纯度 如果是的话 又是如何保持的 我经常读到副作用是如何不纯粹的 但有用的程序 例如 I O 需要副作用 下一句指出 Haskell 对此的解决方案是 mona
  • 如何编写将布尔值返回到一个函数的函数

    我在这里发现了一个类似的问题 它问了几乎相同的问题 但又不完全一样 我的问题是如何将 a gt Bool 类型的函数列表组合成一个也是 a gt Bool 的函数 Ex compose a gt Bool gt a gt Bool comp
  • 带边界的 haskell 列表数据类型

    我有以下类型定义来表示卡片 data Suit Hearts Spades Diamonds Clubs data Rank Numeric Integer Jack Queen King Ace data Card Card Rank S
  • 为什么 Haskell 没有 I Monad(仅用于输入,与 IO monad 不同)?

    从概念上讲 执行输出的计算似乎与仅执行输入的计算有很大不同 从某种意义上说 后者更为纯粹 就我而言 我希望有一种方法将程序中仅输入的部分与可能实际写出内容的部分分开 那么 为什么没有输入只有 Monad 呢 为什么拥有一个 I monad

随机推荐

  • NHibernate 查询建模

    通常我会将我的 criterias hql 查询放在与实体相关的存储库 dal 类中 但最近我一直在考虑添加另一个表示查询是什么的抽象 这将使我有可能将常见行为添加到基类中的所有查询 例如分页 等 现在这些就是我的组件 与 nhiberna
  • 在常规发布请求中设置标头

    我需要设置一个header in a post请求 授权 request token 我尝试过使用 wslite 和 groovyx net http HTTPBuilder 但我总是得到 401 未授权 这意味着我无法正确设置标头 我也想
  • 在 terraform 中构建输出地图

    我有一个要创建的用户列表 一个 sns 主题列表以及创建策略以向用户授予主题权限 这些都是针对用户的命名空间 Given main tf provider aws region eu west 1 profile terraform mod
  • 将调用命令的输出封装在变量中 - PowerShell

    我有一个在远程计算机 来自 DC 上安装远程桌面服务的脚本 我现在正处于检查 RDS 是否安装在连接代理 服务器 和连接主机 服务器 上的阶段 我想使用调用命令 因为远程 powershell 会话似乎太复杂了 这是我的代码 res Inv
  • 在 Windows 中使用子进程运行 python 脚本。来自 emacswiki 的 Python 代码检查器包装器产生相同的错误

    所以我正在尝试设置 emacs wiki 中建议的 python 代码检查器 但是 我无法在命令 shell 中运行这些脚本 更不用说 emacs 了 该部分可在此处找到 http www emacswiki org emacs Pytho
  • 将字符串存储到c中的数组中

    据我所知 我可以创建一个包含项目的数组 例如 char test1 3 arrtest ao 123 但是我如何将我的输入存储到上面的代码之类的数组中 因为我只能将其编码为 input 10 scanf s input or gets in
  • 在运行时更改 iOS 模拟器的当前区域设置

    在开发一组用于将数值和日期转换为字符串的日期计算和语言规则时 我正在编写断言字符串格式化方法的结果的测试 一个虚构的断言可能如下所示 NSAssert dateString isEqualToString Three days until
  • Python-撤消标准输出重定向

    所以我知道从 在Python中将标准输出重定向到 无 https stackoverflow com questions 6735917 redirecting stdout to nothing in python 您可以抑制 print
  • os.kill 没有引发 OSError,但是我没有看到给定的 pid 正在运行

    在我的 ubuntu 服务器上运行以下命令 python c import os os kill 5555 0 这样做是为了查看 pid 5555 是否正在运行 根据我的理解 如果 pid 没有运行 这应该会引发 OSError 这不会对我
  • 已存在记录检查的逻辑,但仅在更新表单值的情况下[重复]

    这个问题在这里已经有答案了 我正在开发一个名为县经理的模块 我在检查县 mysql 表中已存在的县及其国家 地区时遇到问题 Database table 让我解释 Add Page In add page i am having 2 fie
  • [String] 与 [(String)] 有什么区别?

    Swift 中 String 和 String 有什么区别 我让他们使用let arr1 String and let arr2 String 应该没有什么区别 如果你看到的话 这是 Xcode 或 Swift 中的一个小故障 String
  • 如何使用 std::copy 将一张地图复制到另一张地图?

    我想将一个 std map 的内容复制到另一个 std map 中 我可以用吗std copy为了那个原因 显然 下面的代码是行不通的 int main typedef std map
  • 跟踪对 Delphi 中的文件夹所做的更改

    我需要编写一个 Delphi 程序来监视文件夹的更改 添加 更新 重命名和删除文件 我看到了使用 TShellChangeNotifier 的建议 这是这个问题的正确解决方案吗 我应该如何使用它 This question https st
  • 从 SQL Server 检索数据并将其转换为 json 格式?

    我正在使用 PHP 5 6 0 并连接到我的本地 SQL Server 我能够检索数据 但它是数组格式 我想把它转换成json格式 我得到什么 date gt 2013 02 05 16 02 02 000000 timezone type
  • C++ 将字符串转换为十六进制,反之亦然

    在 C 中将字符串转换为十六进制或反之亦然的最佳方法是什么 Example 像这样的字符串 Hello World 转为十六进制格式 48656C6C6F20576F726C64 并从十六进制48656C6C6F20576F726C64字符
  • 尝试跟踪 Firefox 中未完成的 AJAX 请求的数量

    我正在使用 Selenium 来测试 Web 应用程序 并且不允许修改应用程序的 javascript 代码 我试图通过使用 GreaseMonkey 覆盖 XMLHttpRequest send 来跟踪未完成的 AJAX 请求的数量 新的
  • 为什么静态数据成员可能无法初始化?

    我试图在加载时向工厂注册一堆类 我的策略是利用静态初始化来确保在 main 开始之前 工厂已准备就绪 当我动态链接库时 此策略似乎有效 但当我静态链接时则无效 当我静态链接时 只有一些静态数据成员被初始化 假设我的工厂生产汽车 我有 Car
  • 需要了解使用 RAMDirectory 的优缺点

    我需要提高 Lucene 搜索查询的性能 我可以使用 RAMDirectory吗 它可以优化性能吗 这有索引大小限制吗 如果有人可以列出使用 RAMDirectory 的优点和缺点 我将不胜感激 Thanks 我比较了 FSDirector
  • WiX 安装程序:产品最终用户协议显示虚拟文本

    我有一个现有的安装项目 最终用户许可证对话框显示虚拟文本 Lorum ipsum 而不是默认协议 我一直在尝试解决该问题 但我不知道要更改什么以及如何获取默认的最终用户许可协议 我没有发布代码 因为它有很多公司的网址 但这里有一个 UI 节
  • 目录树的广度优先遍历并不懒惰

    我尝试遍历目录树 简单的深度优先遍历似乎不会以惰性方式生成数据 并且会耗尽内存 接下来 我尝试了广度优先方法 该方法显示了相同的问题 它使用了所有可用内存 然后崩溃 我的代码是 getFilePathBreadtFirst FilePath