Прежде всего, я думаю, что ответы, которые дали другие, будут работать по крайней мере в 95% случаев. Рекомендуется всегда кодировать проблему, используя соответствующие типы данных (или кортежи в некоторых случаях). Однако иногда вы действительно не знаете заранее, что ищете в списке, и в этих случаях попытка перечислить все возможности трудна / трудоемка / подвержена ошибкам. Или вы пишете несколько вариантов одного и того же вида вещей (вручную вставляя несколько сгибов в один) и хотите захватить абстракцию.
К счастью, есть несколько техник, которые могут помочь.
Каркасное решение
(в некоторой степени само-проповедующий)
Во-первых, различные пакеты "iteratee / enumerator" часто предоставляют функции для решения подобных проблем. Я наиболее знаком с iteratee , который позволит вам сделать следующее:
import Data.Iteratee as I
import Data.Iteratee.Char
import Data.Maybe
-- first, you'll need some way to process the Atoms/Sheets/etc. you're getting
-- if you want to just return them as a list, you can use the built-in
-- stream2list function
-- next, create stream transformers
-- given at :: B.ByteString -> Maybe Atom
-- create a stream transformer from ByteString lines to Atoms
atIter :: Enumeratee [B.ByteString] [Atom] m a
atIter = I.mapChunks (catMaybes . map at)
otIter :: Enumeratee [B.ByteString] [Sheet] m a
otIter = I.mapChunks (catMaybes . map ot)
-- finally, combine multiple processors into one
-- if you have more than one processor, you can use zip3, zip4, etc.
procFile :: Iteratee [B.ByteString] m ([Atom],[Sheet])
procFile = I.zip (atIter =$ stream2list) (otIter =$ stream2list)
-- and run it on some data
runner :: FilePath -> IO ([Atom],[Sheet])
runner filename = do
resultIter <- enumFile defaultBufSize filename $= enumLinesBS $ procFile
run resultIter
Одно из преимуществ, которое это дает, - дополнительная возможность компоновки. Вы можете создавать трансформеры так, как вам нравится, и просто комбинировать их с zip. Вы даже можете запускать потребителей параллельно, если хотите (хотя только если вы работаете в монаде IO
и, вероятно, не стоит, если потребители не выполняют много работы), изменив это на:
import Data.Iteratee.Parallel
parProcFile = I.zip (parI $ atIter =$ stream2list) (parI $ otIter =$ stream2list)
Результат этого не совпадает с одним циклом for - он по-прежнему будет выполнять несколько обходов данных. Однако картина обхода изменилась. Это позволит загружать определенное количество данных за один раз (defaultBufSize
байт) и проходить через этот фрагмент несколько раз, сохраняя при необходимости частичные результаты. После того, как чанк полностью израсходован, загружается следующий чанк, а старый можно собирать мусором.
Надеюсь, это продемонстрирует разницу:
Data.List.zip:
x1 x2 x3 .. x_n
x1 x2 x3 .. x_n
Data.Iteratee.zip:
x1 x2 x3 x4 x_n-1 x_n
x1 x2 x3 x4 x_n-1 x_n
Если вы делаете достаточно работы, чтобы параллелизм имел смысл, это вовсе не проблема. Из-за локальности памяти производительность намного лучше, чем множественные обходы по всему вводу, как Data.List.zip
.
Прекрасное решение
Если решение с одним обходом действительно имеет смысл, вас могут заинтересовать пост Макса Рабкина Beautiful Folding и сообщение Конала Эллиота продолжение работа ( это тоже ). Основная идея заключается в том, что вы можете создавать структуры данных, представляющие сгибы и почтовые индексы, и их объединение позволяет создать новую комбинированную функцию сгиба / почтового индекса, которая требует только одного обхода. Это может быть немного продвинутым для новичка в Haskell, но, так как вы думаете о проблеме, вы можете найти ее интересной или полезной. Пост Макса, вероятно, лучшая отправная точка.