Haskell 如何读取大的csv文件?

Haskell 如何读取大的csv文件?,haskell,Haskell,我试图读取haskell的一个大csv文件,并按每列生成字数 这将导致文件中超过4M行 因此,我选择读取一个块,并获得每次的字数(一个块5k行)。 然后把它加起来 当我用12000行和120000行测试函数时,时间几乎呈线性增长。 但当读取180000行时,运行时间超过四倍以上 我认为这是因为内存不够,用磁盘交换会使功能慢很多 我必须以map/reduce风格编写代码,但是如何使haskell不在内存中保存所有数据呢 打击是我的代码和分析结果 import Data.Ord import Tex

我试图读取haskell的一个大csv文件,并按每列生成字数

这将导致文件中超过4M行

因此,我选择读取一个块,并获得每次的字数(一个块5k行)。 然后把它加起来

当我用12000行和120000行测试函数时,时间几乎呈线性增长。 但当读取180000行时,运行时间超过四倍以上

我认为这是因为内存不够,用磁盘交换会使功能慢很多

我必须以map/reduce风格编写代码,但是如何使haskell不在内存中保存所有数据呢

打击是我的代码和分析结果

import Data.Ord
import Text.CSV.Lazy.String
import Data.List
import System.IO
import Data.Function (on)
import System.Environment

splitLength = 5000


mySplit' [] = []
mySplit' xs = [x] ++  mySplit' t 
    where
    x = take splitLength xs 
    t = drop splitLength xs                

getBlockCount::Ord a => [[a]] -> [[(a,Int)]]
getBlockCount t =   map 
    (map (\x -> ((head x),length x))) $ 
    map group $ map sort $ transpose t

foldData::Ord a=> [(a,Int)]->[(a,Int)]->[(a,Int)]
foldData lxs rxs = map combind wlist
    where
        wlist = groupBy ((==) `on` fst) $ sortBy (comparing fst) $ lxs ++ rxs
        combind xs 
         | 1==(length xs) = head xs
         | 2 ==(length xs) = (((fst . head) xs ), ((snd . head) xs)+((snd . last) xs))


loadTestData datalen = do
    testFile <- readFile "data/test_csv"
    let cfile = fromCSVTable $ csvTable $ parseCSV testFile
    let column = head cfile
    let body = take datalen $ tail cfile
    let countData = foldl1' (zipWith  foldData)  $ map  getBlockCount  $ mySplit' body 
    let output =  zip column $ map ( reverse . sortBy (comparing snd) ) countData
    appendFile "testdata" $ foldl1 (\x y -> x ++"\n"++y)$ map show $tail output

main = do 
    s<-getArgs
    loadTestData $ read  $ last s

我以前曾用另一种语言遇到过这个问题。诀窍不是将数据读入内存,而是一次只读取一行数据。当您阅读下一行时,只需覆盖您的变量,因为您只需要查找字数。 只需在io流中测试EOF文件结束条件,然后退出即可。这样你就不需要了;我不必拆分文件


希望这能有所帮助。首先,我有几点建议

  • 列表并不快。好吧,好吧,cons是常数时间,但一般来说,列表并不快。您正在使用列表。(Data.Sequence对于双端连接和消费来说会更快)

  • 弦很慢。字符串很慢,因为它们是[Char](字符列表)。您当前使用的库是根据字符串列表编写的。通常,字符链表的链表不是文本处理所需要的。这不是布埃诺。以后使用文本(用于,呃,文本)或ByteString(用于字节)代替字符串,除非它很小且对性能不敏感

  • 您正在使用的库只是惰性的,不是流式的。您必须处理将流行为覆盖到惰性语义上的问题,以获得持续的内存使用。流媒体库解决了增量处理数据和限制内存使用的问题。我建议学习管道或导管来解决这类问题。一些特定于问题的库还将提供可用于流的iteratee API。iterateeapi可以直接使用,也可以连接到管道/导管等

  • 我认为你正在使用的图书馆不是个好主意

    我建议您使用以下库之一:

    (基于管道)

    (通用CSV库,不基于特定流媒体库)

    (基于导管)


    无论您在积累什么,这些都会给您带来良好的性能和恒定的内存使用模数。

    有几件事需要注意:

  • 您希望流式传输数据,以便在任何时候都只在内存中保存输入文件的一小部分。您可能可以使用lazy IO和
    lazy csv
    包来实现这一点。然而,仍然很容易在不经意间保留将所有输入保留在内存中的引用。更好的选择是使用流媒体库,如
    csv-conduct
    pipes-csv

  • 处理大量字符串数据时,请使用
    ByteString
    Text

  • 您希望确保在减少数据时使用严格的操作。否则,您将在内存中积累大量未计算的表达式,直到打印结果的最后一刻。thunks可能在你的
    foldata
    函数中建立起来——字数表达式似乎没有减少

  • 下面是一个程序示例,它将计算CSV文件每列中所有单词的总长度,并在恒定内存中进行计算。其主要特点是:

    • 使用惰性IO
    • lazy csv
      包与(lazy)
      ByteString
      一起使用,而不是
      String
    • 使用
      模式
      来限制行数的计算
    • 使用未装箱数组来保存列计数器
    守则:

    {-# LANGUAGE BangPatterns #-}
    
    import qualified Data.ByteString.Lazy.Char8 as BS
    import Data.ByteString.Lazy (ByteString)
    import Text.CSV.Lazy.ByteString
    import System.Environment (getArgs)
    import Data.List (foldl')
    import Data.Int
    import Data.Array.IO
    import Data.Array.Unboxed
    import Control.Monad
    
    type Length = Int64 -- use Int on 32-bit systems
    
    main = do
      (arg:_) <- getArgs
      (line1:lns) <- fmap BS.lines $ BS.readFile arg
    
      -- line1 contains the header
      let (headers:_) = [ map csvFieldContent r | r <- csvTable (parseCSV line1) ]
          ncols = length headers :: Int
    
      arr <- newArray (1,ncols) 0 :: IO (IOUArray Int Length)
      let inc i a = do v <- readArray arr i; writeArray arr i (v+a)
    
      let loop !n [] = return n
          loop !n (b:bs) = do
            let lengths = map BS.length $ head [ map csvFieldContent r | r <- csvTable (parseCSV b) ]
            forM_ (zip [1..] lengths) $ \(i,a) -> inc i a
            loop (n+1) bs
      print headers
      n <- loop 0 lns
      putStrLn $ "n = " ++ show (n :: Int)
      arr' <- freeze arr :: IO (UArray Int Length)
      putStrLn $ "totals = " ++ show arr'
    
    {-#语言模式}
    将限定数据.ByteString.Lazy.Char8作为BS导入
    导入Data.ByteString.Lazy(ByteString)
    导入Text.CSV.Lazy.ByteString
    导入System.Environment(getArgs)
    导入数据列表(foldl')
    导入数据.Int
    导入Data.Array.IO
    导入Data.Array.unbox
    进口管制
    type Length=Int64——在32位系统上使用Int
    main=do
    
    (arg:u)您需要使用流媒体库,如
    csv conduct
    pipes csv
    “当您阅读下一行时,只需覆盖您的变量,因为您只需要查找字数。”这是Haskell。我们没有“变量”
    {-# LANGUAGE BangPatterns #-}
    
    import qualified Data.ByteString.Lazy.Char8 as BS
    import Data.ByteString.Lazy (ByteString)
    import Text.CSV.Lazy.ByteString
    import System.Environment (getArgs)
    import Data.List (foldl')
    import Data.Int
    import Data.Array.IO
    import Data.Array.Unboxed
    import Control.Monad
    
    type Length = Int64 -- use Int on 32-bit systems
    
    main = do
      (arg:_) <- getArgs
      (line1:lns) <- fmap BS.lines $ BS.readFile arg
    
      -- line1 contains the header
      let (headers:_) = [ map csvFieldContent r | r <- csvTable (parseCSV line1) ]
          ncols = length headers :: Int
    
      arr <- newArray (1,ncols) 0 :: IO (IOUArray Int Length)
      let inc i a = do v <- readArray arr i; writeArray arr i (v+a)
    
      let loop !n [] = return n
          loop !n (b:bs) = do
            let lengths = map BS.length $ head [ map csvFieldContent r | r <- csvTable (parseCSV b) ]
            forM_ (zip [1..] lengths) $ \(i,a) -> inc i a
            loop (n+1) bs
      print headers
      n <- loop 0 lns
      putStrLn $ "n = " ++ show (n :: Int)
      arr' <- freeze arr :: IO (UArray Int Length)
      putStrLn $ "totals = " ++ show arr'