2017-06-28 5 views
1

私は、csvファイルの列を並べ替える最速の方法を見つけようとしています(セルにカンマがない単純なcsvサブセットを使用しています)。私がVector.backpermuteで行っている並べ替えとそれは問題ありません。 RTS -pで示されるボトルネックは、この操作を行うベクトルのベクトルの構築です。以下のコードは私が思いつくことができる最速のバージョンです。誰にでもアイデアはありますか?あなたは、引数の解析ビットを無視することができますので、私はそのcsvファイルのすべての行の第二、その後、最初の列を与えると言う$ reorder 2,1 x.csvcsvファイルをベクトルのベクトルに解析してから吐き出す最速の方法は?

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import   Control.Applicative 
import   Control.Monad 
import qualified Data.ByteString   as B 
import qualified Data.ByteString.Builder as BB 
import qualified Data.ByteString.Lazy  as BL 
import qualified Data.ByteString.Lazy.Char8 as BL8 
import   Data.Char 
import   Data.Foldable 
import   Data.Monoid 
import qualified Data.Vector    as V 
import   Data.Word 
import   Debug.Trace 
import   System.Environment 
import   System.IO 

data Args = Args { cols :: V.Vector Int, filePath :: FilePath } deriving (Show) 

-- 
w8 = fromIntegral . ord 
mconcat' :: (Foldable t, Monoid a) => t a -> a 
mconcat' = foldl' (<>) mempty 

parseArgs :: [String] -> Args 
parseArgs [colStr, filePath] = Args ((\n -> n-1) . read <$> V.fromList (split ',' colStr)) filePath 
    where split :: Char -> String -> [String] 
     split d str = gosplit d str [] 
     gosplit d "" acc = reverse acc 
     gosplit d str acc = gosplit d (drop 1 $ dropWhile (/= d) str) $ takeWhile (/= d) str : acc 

reorder :: Args -> BL.ByteString -> BB.Builder 
reorder (Args cols _) bstr = 
    -- transform to vec matrix 
    let rows = V.filter (not . BL.null) $ V.fromList $ BL.split (w8 '\n') bstr 
     m = (V.fromList . BL.split (w8 ',')) <$> rows -- n^2 
    -- reorder 
     m' = (flip V.backpermute) cols <$> m 
    -- build back to bytestring 
     numRows = length m' 
     numCols = length cols 
     builderM = mconcat' . V.imap (\i v -> BB.lazyByteString v <> (if i < numCols - 1 then "," else "")) <$> m' 
     builderM' = mconcat' . V.imap (\i v -> v <> (if i < numRows - 1 then "\n" else "")) $ builderM 
    in builderM' 

main :: IO() 
main = do 
    args <- parseArgs <$> getArgs 

    withFile (filePath args) ReadMode $ \h -> do 
    csvData <- BL.hGetContents h 
    BB.hPutBuilder stdout $ reorder args csvData 

プログラム

は次のように呼び出されます。

答えて

1

あなたはあまりにも頑張っているように感じます。このデータをすべて手動で構築して変換するのはエラーが起こりやすく、理由が分かりにくい(少なくとも私にとっては)。この種のタスクのためにcassavaが作成されます。

あなたが提示したコードからデータの構造を完全にアンラングすることはできません。そのため、私は簡単な例を使用して目標を達成する方法を示します。 "そのような列を並べ替える" 。

人とその年齢のリストを記述するCSVがあるとします。

{-# LANGUAGE OverloadedStrings #-} 
{-# LANGUAGE DeriveGeneriC#-} 

import Data.Text 
import Data.Csv 
import Data.Vector 

data Person = Person { name :: !Text , age :: !Int } deriving (Generic, Show) 

-- We want to read and write TSVs 

decodeOpt :: DecodeOptions 
decodeOpt = defaultDecodeOptions { decDelimiter = fromIntegral (ord '\t') } 

encodeOpt :: EncodeOptions 
encodeOpt = defaultEncodeOptions { encDelimiter = fromIntegral (ord '\t') } 

-- NB: Ideally, your encode and decode should be inverses, but these aren't 
dec :: FromRecord a => HasHeader -> ByteString -> Either String (Vector a) 
dec = decodeWith decodeOpt 

enc :: ToRecord a => [a] -> ByteString 
enc = encodeWith encodeOpt 

は、今、私たちはいくつかの魔法やるつもりだ:

instance FromRecord Person 

instance ToRecord Person where 
    toRecord (Person name age) = record [ toField age, toField name ] 

をそして今、我々は

dec NoHeader "Roy\t30\r\nJim\t32" :: Either String (Vector Person) 

を取ることができ、

Right [Person {name = "Roy", age = 30} 
     ,Person {name = "Jim", age = 32}] 

を取得し、それらを

を再シリアライズ私たちの結果

"30\tRoy\r\n32\tJim\r\n" 

enc [Person "Roy" 30, Person "Jim" 32] 

だから、これはあなただけでインデックスベースのカラム操作に興味を持っていると仮定するとすべてが順調と良いです。 CSVに列名が付いている場合は、事柄についてさらに直接的な対応が可能です。

instance ToNamedRecord Person 
instance DefaultOrdered Person 
instance FromNamedRecord Person 

-- NB: Ideally, your encode and decode should be inverses, but these aren't 
decName ::FromNamedRecord a => ByteString -> Either String (Header, Vector a) 
decName = decodeByNameWith decodeOpt 

encName :: ToNamedRecord a => [a] -> ByteString 
encName = encodeByNameWith encodeOpt (header ["age", "name"]) 

今、私たちは、この

encName [Person "Roy" 30, Person "Jim" 32] 

を行うと、

を取得するために

"age\tname\r\n30\tRoy\r\n32\tJim\r\n" 

または

decName "name\tage\r\nRoy\t30\r\nJim\t32" :: Either String (Header, Vector Person) 

を得ることができます

最後に、実際に構造が必要ない場合は、cassavaもそれに対処できます。私たち

Right [["Roy","30"],["Jim","32"]] 

そして

enc [["Roy","30"],["Jim","32"]] 

を与える

dec NoHeader "Roy\t30\r\nJim\t32\r\n" :: Either String (Vector (Vector ByteString)) 

は私たちにこの場合

"Roy\t30\r\nJim\t32\r\n" 

を与え、彼らは定期的にリストしているので、あなたは何でも行うことができますサブリストで好きな変換を元に戻すあなたが好きなように列をアンジュ。

+0

お返事ありがとうございます!私は最初の数回の繰り返しで実際にキャッサバを試しましたが、ちょうどそうでしたので、ずっと遅くなりました... – asg0451

+0

Aww、それは残念です。私は 'pipes-csv'についても考えていました。おそらく、いくつかのストリーミングプロパティを悪用しようとしているので、より小さなデータセットでメイン操作を行うことができます。これは、ベクトルの1つの巨大ベクトルを作成してから、それをByteStringに書き戻す前にすべて並べ替えるように見えます。一度に1つの行だけを考え、最後にビルダーに結果をストリーミングするだけで、マトリックスを再構築するのではなく、より良いパフォーマンスを得ることができます。 –

+0

ええ。私もそのアプローチを試みた。まだ非常に遅いです。ベンチマークは計算時間がすべてキャッサバにあることを示していたため、私はそれをカットして未加工ベクトルに頼っていましたが、それでもまだ建設コストがかかりますが、それよりも小さいものでした。 私はそれを使用していたときにキャッサバが私の最大のボトルネックだったので、私はパイプ-CSVを試しました。 – asg0451

関連する問題