Haskellの強みの一つは、関数合成やパターンマッチを使って複雑なロジックをバグを抑えながら書けることだと思います。
さくっとデータ処理をやりたい時に使えそうなところをまとめました。
まずはシンプルに
ghciとdoctest
対話型のghciを使うと試しながらできるのでオススメです。
そこである程度固まってきたら、hsファイルにまとめたくなると思います。
doctestは、簡単な実行環境としても使えます。 Haskellの単体テスト最前線
Listを使う
列ごとにListとして取り込みます。
行は、Listをzipしてtupleを作ると柔軟に使えます。
Record構文は、さくっとやりたい時には、手間がかかります。
>>> id = ["a","b","c"]
>>> num = [1,2,3]
>>> data = zip id num
>>> data
[("a", 1), ("b", 2), ("c", 3)]
モナドについて
IOは出力に必要ですが、それ以外は最低限必要なものだけを使います。
実践的にモナドを使う方針と具体的な使い方が参考になります。
Maybeは、欠損値がある場合に使います。
型宣言しない
型宣言をつけると格好がつきますが、さくっとやる場合は、基本つけなくてもいいと思います。
少し実践的に
Vector
パフォーマンスを考えたらぜひ使いたいライブラリです。
List操作は楽ですが、パフォーマンスがネックになる場合があります。
その時に使えます。
また、データ処理系のライブラリでは、デフォルトで使われています。
https://github.com/haskell/vector
csvを使う
cassavaがシンプルに使えて便利です。
https://github.com/hvr/cassava
サクッと使うならば、ヘッダなしがオススメ。 レコードごとに処理するサンプルコードです。
{-# LANGUAGE ScopedTypeVariables #-}
import qualified Data.ByteString.Lazy as BL
import Data.Csv
import qualified Data.Vector as V
main :: IO ()
main = do
csvData <- BL.readFile "salaries.csv"
case decode NoHeader csvData of
Left err -> putStrLn err
Right v -> V.forM_ v $ \ (name, salary :: Int) ->
putStrLn $ name ++ " earns " ++ show salary ++ " dollars"
ここで、decode NoHeader csvData
は、Vectorを返すので、forM_
以外のVectorの関数にも慣れておくといいです。
joinを作ってみた
Haskellはロジック記述力が高いので、シンプルにいろいろと書けます。
SQLのjoinに相当する操作(結合条件を指定して、二つの表をつなげる)を探しましたが、意外とありませんでした。
実際にデータを処理するときは、キー結合したいときは結構多いため、書いてみました。
キーを条件に要素をつなげてますが、結合条件を関数で渡してもよかったと思います。 inner joinは、zip
, filter
, intersect
などでそのまま書けます。
import Data.List
import Data.Maybe as M
-- | Join
--
-- Examples:
--
-- >>> fullJoin [1,2] [10,20] [2,3] [200, 300]
-- [(1,Just 10,Nothing),(2,Just 20,Just 200),(3,Nothing,Just 300)]
--
-- >>> leftJoin [1,2] [10,20] [2,3] [200, 300]
-- [(1,Just 10,Nothing),(2,Just 20,Just 200)]
--
-- >>> rightJoin [1,2] [10,20] [2,3] [200, 300]
-- [(2,Just 20,Just 200),(3,Nothing,Just 300)]
--
-- >>> innerJoin [1,2] [10,20] [2,3] [200, 300]
-- [(2,Just 20,Just 200)]
--
-- >>> data Row = Row { col1 :: Integer , col2 :: Integer } deriving (Eq, Show)
-- >>> type Table = [Row]
-- >>> let table = [Row {col1 = 10, col2 = 11}, Row{col1 = 20, col2 = 21}]
-- >>> fullJoin [1,2] table [2,3] [200,300]
-- [(1,Just (Row {col1 = 10, col2 = 11}),Nothing),(2,Just (Row {col1 = 20, col2 = 21}),Just 200),(3,Nothing,Just 300)]
--
-- >>> let { f i (Just x) Nothing = (i, col1 x, col2 x, 0); f i (Just x) (Just y) = (i, col1 x, col2 x, y); f i Nothing (Just y) = (i, 0, 0, y); f i Nothing Nothing = (i, 0, 0, 0); }
-- >>> fullJoinWith f [1,2] table [2,3] [200,300]
-- [(1,10,11,0),(2,20,21,200),(3,0,0,300)]
fullJoinWith f ks1 vs1 ks2 vs2 = map (\(i, x, y) -> f i x y) $ fullJoin ks1 vs1 ks2 vs2
leftJoin :: Ord k => [k] -> [a] -> [k] -> [b] -> [(k, M.Maybe a, M.Maybe b)]
leftJoin ks1 vs1 ks2 vs2 = _join ks1 ks1 vs1 ks2 vs2
rightJoin :: Ord k => [k] -> [a] -> [k] -> [b] -> [(k, M.Maybe a, M.Maybe b)]
rightJoin ks1 vs1 ks2 vs2 = map (\(x, y, z) -> (x, z, y)) $ _join ks2 ks2 vs2 ks1 vs1
fullJoin :: Ord k => [k] -> [a] -> [k] -> [b] -> [(k, M.Maybe a, M.Maybe b)]
fullJoin ks1 vs1 ks2 vs2 = _join (union ks1 ks2) ks1 vs1 ks2 vs2
innerJoin :: Ord k => [k] -> [a] -> [k] -> [b] -> [(k, M.Maybe a, M.Maybe b)]
innerJoin ks1 vs1 ks2 vs2 = _join (intersect ks1 ks2) ks1 vs1 ks2 vs2
_join :: Ord k => [k] -> [k] -> [a] -> [k] -> [b] -> [(k, M.Maybe a, M.Maybe b)]
_join ids ks1 vs1 ks2 vs2 = zipWith(\(x, y) (_, y2) -> (x, y, y2)) zs1 zs2
where
zs1 = mkZs ks1 vs1
zs2 = mkZs ks2 vs2
mkZs ks vs = sortByFst . filterByFst . (++) (zipWith(\k v -> (k, Just v)) ks vs) $ zip (ids \\ ks) (repeat Nothing)
sortByFst = sortBy (\(x1, _) (x2, _) -> compare x1 x2)
filterByFst = filter (flip elem ids . fst)
0 件のコメント:
コメントを投稿