LoginSignup
2
0

More than 3 years have passed since last update.

fsharpでcsvを扱ってみる!(発展途上です!)

Last updated at Posted at 2019-12-22

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

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

現場からは以上です!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0