🦀

C++erのRust入門4:CSV

2023/05/06に公開

Rust で CSV を読む方法について。

CSV クレートの追加

Rust で CSV を読む場合は、 csv クレート を使うのが一般的らしい。

cargo add csv

CSV をとりあえず読み込む

手っ取り早く取りあえず CSV ファイルを読むだけなら、以下が一番手っ取り早いみたい。

https://github.com/nodamushi/zenn-program/blob/ee9cc8c68a40ad59ecb66935411b34011340ccc3/src/rust/study/csv_sample/src/main.rs#L4-L11

サンプルデータは私がこの1年ほど体重をCSVで記録してるので、それを適当に4行ほど切り出したもの。

https://github.com/nodamushi/zenn-program/blob/main/src/rust/study/csv_sample/weight_data.csv

  • year: 整数 年
  • month: 整数 月
  • day: 整数 日付
  • hour: 整数 時
  • minute: 整数 分
  • weight: 浮動小数点数 デブ加減[kg]
  • fat: 浮動小数点数 デブ度[%]
    • 最初は記録してなかったので ?? というデータあり

実行結果はこんな感じになった。

StringRecord(["2022", "3", "20", "5", "58", "92.9", "??"])
StringRecord(["2022", "7", "30", "10", "29", "84.7", "??"])
StringRecord(["2022", "7", "31", "6", "41", "85.5", "25.0"])
StringRecord(["2023", "5", "1", "13", "45", "75.8", "20.8"])

型に変換する

StringRecord だとインデックスでアクセスする。

それよりは専用の型に変換した方が何かと扱いやすいよね。

#[derive(Debug, Copy, Clone)]
struct Entry {
  year: u16,
  month: u8,
  day: u8,
  hour: u8,
  minute: u8,
  weight: f32,
  fat: Option<f32>
}

手動で変換してもいいけど、csv クレートSerde クレート という型を Serialize/Deserialize するためのクレートに対応しているらしく、これを使うのが簡単みたい。

Serde クレートの追加

以下のコマンドで Serde クレート をプロジェクトに追加する。

-F derive をつけないと #[derive] が使えない。

cargo add serde -F derive

Serialize, Deserializeの実装

単純なものは #[derive(Serialize, Deserialize)] とするだけで実装できてしまう。

https://github.com/nodamushi/zenn-program/blob/ee9cc8c68a40ad59ecb66935411b34011340ccc3/src/rust/study/csv_sample/src/main.rs#L13-L14

ただ、今回の体脂肪率 fatOption で、このままではエラーが発生する。

なので、独自にシリアライズとデシリアライズの関数をfat_formatモジュールに実装する。

https://github.com/nodamushi/zenn-program/blob/ee9cc8c68a40ad59ecb66935411b34011340ccc3/src/rust/study/csv_sample/src/main.rs#L21-L22

  • Serialize: pub fn serialize<S>(value: &型, s: S) -> Result<S::Ok, S::Error> where S: Serializer
  • Deserialize: pub fn deserialize<'d, D>(d: D) -> Result<型, D::Error> where D: Deserializer<'d>

なお、どちらも pub が必須で、型は今回は Option<f32>である。

https://github.com/nodamushi/zenn-program/blob/ee9cc8c68a40ad59ecb66935411b34011340ccc3/src/rust/study/csv_sample/src/main.rs#L25-L45

CSV からデシリアライズする

reader.records()reader.deserialize() にして、 let data: EntryEntry を指定する。

https://github.com/nodamushi/zenn-program/blob/ee9cc8c68a40ad59ecb66935411b34011340ccc3/src/rust/study/csv_sample/src/main.rs#L47-L56

結果は以下のようになった。

Entry { year: 2022, month: 3, day: 20, hour: 5, minute: 58, weight: 92.9, fat: None }
Entry { year: 2022, month: 7, day: 30, hour: 10, minute: 29, weight: 84.7, fat: None }
Entry { year: 2022, month: 7, day: 31, hour: 6, minute: 41, weight: 85.5, fat: Some(25.0) }
Entry { year: 2023, month: 5, day: 1, hour: 13, minute: 45, weight: 75.8, fat: Some(20.8) }

シリアライズする

read_csv_sample2 で読み出した結果をそのまま標準出力に出してみる。

https://github.com/nodamushi/zenn-program/blob/ee9cc8c68a40ad59ecb66935411b34011340ccc3/src/rust/study/csv_sample/src/main.rs#L58-L64

結果は以下。OKOK.

year,month,day,hour,minute,weight,fat
2022,3,20,5,58,92.9,??
2022,7,30,10,29,84.7,??
2022,7,31,6,41,85.5,25.0
2023,5,1,13,45,75.8,20.8

参考

Discussion