我试图从包含大量列(例如几百个)的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)
这部分内容很笨重而且很脆弱(使用read
和unpack
进行解析,以及错误处理的悲惨借口),但更常见的是,我很惊讶我必须写这一点 - 我预计这将存在于Data.Csv
API中,我想知道它是否存在,而我根本就没有认识到它,或者是否存在&# #39;我应该看看的其他一些包。
任何有关更好方法的建议都将不胜感激。
答案 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)(能够在出错的情况下显示字段)并通过仿函数对你的整个记录进行参数化。