LoginSignup
0

More than 3 years have passed since last update.

posted at

updated at

Organization

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

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

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

現場からは以上です!

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
What you can do with signing up
0