fsharpでcsvを扱ってみる！（発展途上です！）

# Summary

`F#`で`CSV`をあつかうあれこれ

## モチベーション

お仕事で、`CSV`ファイルを扱うことが多く、`VBA`でなんとかしてましたが、平気で３００マソとか６００マソとかのレコード数になるので、できれば`fsharp`で扱いたい

## Env.

```
>ver
Microsoft Windows [Version 10.0.18363.535]
```

## 方針

できればドット`.`を押すとカラム名がでて書きやすいのがよきよき（クエリの使用）

## prepare

`fsharp.data`をナゲットしておく

### その１（CSVの扱いの基本）

```fsharp

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")]
```

### その２（CSVの扱いの基本２ - 複数CSV）

`Left Outer Join`を使用して２つ以上のCSVからデータを抽出する

thanks: @hiki_neet_p 

```fsharp
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")]
```

### その３　CSVファイル

ここはよくわかってないところで、`shift_jis`の場合に読み込みができたりできなかったりがあるような・・・（会社のパソコンだと読み込めなかった気が・・・（TODO 要確認）

`ヘッダ読み込み`

||UTF-8|ShiftJIS|
|---|:-----:|:----:|
|日本語|●|x|
|英語|●|●|

`サンプル的な`


```fsharp
// 自作の便利関数
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
```

```fsharp

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: もう少しまとめる。。。

ご指摘等あればぜひぜひ！

現場からは以上です！