我有一个类似于 index : label
的文件,index 的值包含 0...100000000
范围内的键> 和 label 可以是任何 String
值,我想将这个包含 110 Mo 的文件拆分为多个切片,每个切片各 100 行,并对每个切片进行一些计算。我怎样才能做到这一点?
123 : "acgbdv"
127 : "ytehdh"
129 : "yhdhgdt"
...
9898657 : "bdggdggd"
最佳答案
如果您使用 String IO,则可以执行以下操作:
import System.IO
import Control.Monad
-- | Process 100 lines
process100 :: [String] -> MyData
-- whatever this function does
loop :: [String] -> [MyData]
loop lns = go [] lns
where
go acc [] = reverse acc
go acc lns = let (this, next) = splitAt 100 lns in go (process100 this:acc) next
processFile :: FilePath -> IO [MyData]
processFile f = withFile f ReadMode (fmap (loop . lines) . hGetContents)
请注意,此函数将静默处理最后一个 block ,即使它不完全是 100 行。
诸如 bytestring 和 text 之类的包通常提供诸如 lines
和 hGetContents
之类的函数,因此您应该能够轻松地将此函数应用于其中的任何一个。
了解您正在处理每个切片的结果非常重要,因为您不想保留该数据超过必要的时间。理想情况下,在计算每个切片后,数据将被完全消耗并且可以被GC。通常,要么将单独的结果组合成单个数据结构(“折叠”),要么单独处理每个结果(可能将一行输出到文件或类似的东西)。如果是折叠,您应该将“循环”更改为如下所示:
loopFold :: [String] -> MyData -- assuming there is a Monoid instance for MyData
loopFold lns = go mzero lns
where
go !acc [] = acc
go !acc lns = let (this, next) = splitAt 100 lns in go (process100 this `mappend` acc) next
loopFold
函数使用 bang 模式(通过“LANGUAGE BangPatterns”编译指示启用)来强制评估“MyData”。根据 MyData 的具体内容,您可能需要使用 deepseq
来确保它得到充分评估。
如果您将每一行写入输出,请保持 loop
不变并更改 processFile
:
processFileMapping :: FilePath -> IO ()
processFileMapping f = withFile f ReadMode pf
where
pf = mapM_ (putStrLn . show) <=< fmap (loop . lines) . hGetContents
如果您对枚举器/迭代器样式处理感兴趣,这是一个非常简单的问题。如果不知道 process100
正在做什么工作,我就无法给出一个很好的例子,但它会涉及 enumLines
和 take
。
是否有必要一次恰好处理 100 行,还是只想分块处理以提高效率?如果是后者,不用担心。您最好一次处理一行,使用实际的折叠函数或类似于 processFileMapping 的函数。
关于file - 如何使用 Haskell 分割 110Mo 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3698404/