Summary
F#
でCSV
をあつかうあれこれ
モチベーション
お仕事で、CSV
ファイルを扱うことが多く、VBA
でなんとかしてましたが、平気で300マソとか600マソとかのレコード数になるので、できればfsharp
で扱いたい
Env.
>ver
Microsoft Windows [Version 10.0.18363.535]
方針
できればドット.
を押すとカラム名がでて書きやすいのがよきよき(クエリの使用)
prepare
fsharp.data
をナゲットしておく
その1(CSVの扱いの基本)
module Sampel_01 =
open FSharp.Data
// csvを作成
let csv = """
"apple","200","sweety"
"バナナ","120","fresh"
"cherry","450","great"
"""
// schema,providerを作成
[<LiteralAttribute>]
let MySchema = """名前(string),価格(int32),味わい(string)"""
type SampleCsv = CsvProvider<HasHeaders=false,Schema=MySchema>
// クエリを使用してデータを抽出する
query {
for v in SampleCsv.Parse(csv).Rows do
where ( int(v.``価格(int32)``) = 120 )
select v
} |> printfn "%A"
// ===> seq [("バナナ", "120", "fresh")]
その2(CSVの扱いの基本2 - 複数CSV)
Left Outer Join
を使用して2つ以上のCSVからデータを抽出する
thanks: @hiki_neet_p
module Sampel_02 =
open FSharp.Data
open System.Linq
// csvを作成する
let csv = """"apple","200","sweety"
"バナナ","120","fresh"
"cherry","450","great"
"""
let csv2 = """"apple","foo"
"バナナ","bar"
"cherry","baz"
"choco","hoge"
"""
// schema,providerを作成
[<LiteralAttribute>]
let MySchema = """名前(string),価格(int32),味わい(string)"""
type SampleCsv = CsvProvider<HasHeaders=false,Schema=MySchema>
[<LiteralAttribute>]
let MySchema2 = """名前(string),コメント(string)"""
type SampleCsv2 = CsvProvider<HasHeaders=false,Schema=MySchema2>
// クエリを使用してデータを抽出する
query {
for v in SampleCsv2.Parse(csv2).Rows do
leftOuterJoin x in ( SampleCsv.Parse(csv).Rows )
on (v.名前 = x.名前) into result
for z in result.DefaultIfEmpty() do
where (isNull(box z) ) // <--- boxがポイント!
select v
} |> printfn "%A"
// ===> seq [("choco", "hoge")]
その3 CSVファイル
ここはよくわかってないところで、shift_jis
の場合に読み込みができたりできなかったりがあるような・・・(会社のパソコンだと読み込めなかった気が・・・(TODO 要確認)
ヘッダ読み込み
UTF-8 | ShiftJIS | |
---|---|---|
日本語 | ● | x |
英語 | ● | ● |
サンプル的な
// 自作の便利関数
module Callmekohei.CsvHeader
open System.IO
open System.Text
open System.Text.RegularExpressions
open FSharp.Data
module CsvLocalSetting =
// schemaを設定
[<LiteralAttribute>]
let private MySchemaSample02 = """名前(string),コメント(string)"""
// providerを作成
type SampleCsv02 = CsvProvider<HasHeaders=false,Schema=MySchemaSample02>
// csvRowsが格納された配列を返す
let public arrCsvRowsSample02 (hasCsvHeader:bool) (cp932Flg:bool) (fpCSVFile:string) =
// cp932(shiftjis)を取れるようにする
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)
let codepage = if cp932Flg then 932 else 65001
use reader = new StreamReader ( (Path.Combine( Directory.GetCurrentDirectory() , fpCSVFile)) , Encoding.GetEncoding (codepage))
// 異なる改行コードが混じってる dirty csv への対応
let reg = Regex("\r\n", RegexOptions.Compiled)
if hasCsvHeader then reader.ReadToEnd() else Regex.Replace(reader.ReadToEnd(),"^.*\n","")
|> fun s -> reg.Replace( s , "\n" )
|> fun s -> SampleCsv02.Parse( s ).Rows
|> Seq.toArray
module Main =
// open System.Linq
open Callmekohei.CsvHeader.CsvLocalSetting
open FSharp.Data
//Sample00: シンプルなCSVの取り込み方(ヘッダーがある場合, UTF-8のみ)
type CSV = CsvProvider<"./csvFolder/utf8.csv">
let sample00 () =
query{
for v in CSV.GetSample().Rows do
select v.名前
}
//Sample01: シンプルなCSVの取り込み方(ヘッダーがない場合, UTF-8のみ)
type CSV01 = CsvProvider<Sample="./csvFolder/utf8_headerless.csv",Schema="名前,価格,コメント",HasHeaders=false>
let sample01 () =
query{
for v in CSV01.GetSample().Rows do
select [v.名前;v.コメント]
}
// Sample02: sjisなCSVの取り込み方
let sample02 () =
query {
for v in ( arrCsvRowsSample02 false true "./csvFolder/cp932.csv" ) do
select v.名前
}
// Sample03: sjis, utf8なCSVの取り込み方
let sample03 () =
let foo = arrCsvRowsSample02 false true "./csvFolder/cp932.csv"
let bar = arrCsvRowsSample03 false false "./csvFolder/utf8.csv"
query {
for v in foo do
leftOuterJoin x in bar
on (v.名前 = x.名前) into ResultTable
for y in ResultTable do
where ( isNull(box y))
select v
}
[<EntryPoint>]
let main args =
sample00 () |> printfn "%A"
// seq ["apple"; "バナナ"; "cherry"]
sample01 () |> printfn "%A"
// seq [["apple"; "sweety"]; ["バナナ"; "fresh"]; ["cherry"; "great"]]
sample02 () |> printfn "%A"
// seq ["apple"; "バナナ"; "cherry"]
sample03 () |> printfn "%A"
// seq [("choco", "hoge")]
0
その他
CsvProvider
におけるEncoding
はまだUTF-8
以外は対応してなさげ(多分対応する気はない?かも)
結び
TODO: もう少しまとめる。。。
ご指摘等あればぜひぜひ!
現場からは以上です!