Как разделить файл 110Mo с Haskell - PullRequest
2 голосов
/ 13 сентября 2010

У меня есть файл, который выглядит следующим образом: index : label, index 's содержит ключи в диапазоне 0... 100000000, а label может принимать любое значение String,Я хочу разбить этот файл, который содержит 110 Мо на множество срезов по 100 строк в каждом, и сделать некоторые вычисления для каждого среза.Как я могу это сделать?

123 : "acgbdv"

127 : "ytehdh"

129 : "yhdhgdt"

...

9898657 : "bdggdggd"

1 Ответ

3 голосов
/ 14 сентября 2010

Если вы используете 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)

Обратите внимание, что эта функция будет молча обрабатывать последний фрагмент, даже если он не точно 100 строк.

Пакеты, такие как bytestring и text, обычно предоставляют такие функции, как lines и hGetContents, поэтому вы сможете легко адаптировать эту функцию к любому из них.

Важно знать, что вы делаете с результатами обработки каждого среза, потому что вы не хотите удерживать эти данные дольше, чем необходимо. В идеале после расчета каждого среза данные будут полностью использованы и могут быть скопированы. Как правило, отдельные результаты объединяются в одну структуру данных («сворачивание»), или каждый из них рассматривается отдельно (возможно, вывод строки в файл или что-то подобное). Если это фолд, вы должны изменить «цикл», чтобы он выглядел так:

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 использует шаблоны взрыва (включается с помощью прагмы «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.

...