在Haskell中从CSV文件中选择列的好方法是什么?

时间:2018-05-30 03:47:22

标签: haskell

我试图从包含大量列(例如几百个)的CSV文件中获取一个列,而我已经设法使用Cassava包编写了一些代码来执行此操作(又名Data.Csv)我对结果并不满意。即:

import qualified Data.ByteString.Lazy as BSL
import           Data.ByteString.Lazy.Char8 (unpack)
import qualified Data.Csv as CSV
import qualified Data.Vector as V    

-- | Read in the n'th column of a CSV file which has been read into a ByteString
readColumn :: Read a => Int -> CSV.HasHeader -> BSL.ByteString -> V.Vector a
readColumn index headerStatus csvData =
  let recs =
        CSV.decode headerStatus csvData :: Either String (V.Vector (V.Vector BSL.ByteString)) in
    case recs of
      Left err -> error err
      Right rows -> V.map (extractColumn index) rows
  where
    extractColumn :: Read c => Int -> V.Vector BSL.ByteString -> c
    extractColumn n = read . unpack . (V.! n)

这部分内容很笨重而且很脆弱(使用readunpack进行解析,以及错误处理的悲惨借口),但更常见的是,我很惊讶我必须写这一点 - 我预计这将存在于Data.Csv API中,我想知道它是否存在,而我根本就没有认识到它,或者是否存在&# #39;我应该看看的其他一些包。

任何有关更好方法的建议都将不胜感激。

2 个答案:

答案 0 :(得分:0)

这可能会或可能不符合您的需求:它假设您实际上并不想根据自己的位置提取列,而是有兴趣提取您感兴趣的特定列。

{-# LANGUAGE DeriveGeneric #-}
import           Data.Text    (Text)
import           Data.Vector
import           GHC.Generics

import           Data.Csv

data Foo
    = Foo
    { foo :: Text
    , bar :: Int
    } deriving (Eq, Show, Generic)
instance FromNamedRecord Foo

decodeFoo :: _ -> Either String (Header, Vector Foo)
decodeFoo = decodeByName

main :: IO ()
main = do
    let csv = "bar,ignore,foo,ignore\n3,whatever,some string,whatever"
    print $ decodeFoo csv
    -- prints Right (["bar","ignore","foo","ignore"],[Foo {foo = "some string", bar = 3}])

答案 1 :(得分:0)

关于你对FromRecord的评论。 cassava是基于类型的,所以除非你正在解码基本类型的组合,否则不幸的是你提供了一个类型类实例,因此提供了一个Newtype。除了我知道哪个选项具有解析功能(无论如何都是有用的),没有其他选择。我的经验是,在Haskell中类型很便宜(几乎是免费的),并且在需要时创建一次性类型是很常见的。如果您需要100列中的33列,只需为这些字段创建数据类型并为其编写实例。然后,您可以选择实际需要的列。然后你可能会意识到你不想要Int但是(要么是Text Int)(能够在出错的情况下显示字段)并通过仿函数对你的整个记录​​进行参数化。