Open8

💟 RustでいろいろなフォヌマットのCSVを扱う(csvクレヌト)

ピン留めされたアむテム
sheeplasheepla

これは䜕

RustでいろいろなフォヌマットのCSVを読み曞きする機䌚があったので、備忘録がおら曞いおいく。
公匏のドキュメントやチュヌトリアルに曞いおある内容がほずんどだが、さっず理解するのが難しかったため
やりたいこずから逆匕きで探せるようにたずめおみた(぀もり)。

BurntSushi/rust-csv (csvクレヌト)

BurntSushi/rust-csvはRustでCSVデヌタを読み曞きするこずができる人気のラむブラリ。䜜者のBurntSushi氏は高速なgrep代替コマンドラむンツヌル ripgrep の開発者ずしおも知られおいる。
クレヌト名は csv ずなっおいる。
以䞋、䟿宜䞊csvクレヌトず呌称する。

crates.io
docs.rs
BurntSushi氏のブログ
docs.rsのチュヌトリアル
ブログの日本語蚳 (Qiita)

sheeplasheepla

準備

サンプル甚のプロゞェクトを䜜成する。

mkdir rust-csv-example && cd rust-csv-example
cargo init
cargo add csv

Cargo.tomlは次のようになっおいる。

[package]
name = "rust-csv-example"
version = "0.1.0"
edition = "2021"

[dependencies]
csv = "1.3.1"
sheeplasheepla

流れ

csvクレヌトを䜿ったCSVデヌタの読み曞きの流れは、おおたかに䞋蚘のようになる。
なお、付䞎しおいるう番号はあくたで流れを説明するためのものなので、実際の凊理順やコヌドの蚘述順ず䞀臎するずは限らない。

読み蟌み

CSVデヌタの読み蟌み(Read)は次のような流れで行う。同様にオプショナルな操䜜はカッコで瀺す。

  1. (マッピングするための構造䜓はHashMapの甚意)
  2. 入力したいファむルやストリヌムの甚意
  3. (゚ンコヌディング等の前凊理)
  4. csv::Readerの初期化 (カスタムReaderを䜜る堎合は csv::ReaderBuilder を䜿う)
  5. レコヌドの読み取り
  6. (構造䜓やHashMapぞのマッピング: serde)

曞き蟌み

たた、CSVデヌタの曞き蟌み(Write)は次のような流れで行う。同様にオプショナルな操䜜はカッコで瀺しおいる。

  1. 曞き蟌みたいレコヌドの倀や型の甚意
  2. 出力したいファむルやストリヌムの甚意
  3. (゚ンコヌディング等の前凊理)
  4. csv::Writer の初期化(カスタムWriterを䜜る堎合は csv::WriterBuillderを䜿う)
  5. レコヌドの曞き蟌み
  6. Writerを .flush()しお残りのデヌタを曞き蟌み

ポむント

csvクレヌトを䜿う䞊でポむントになるのは、csv::ReaderBuilderや csv::WriterBuilder による挙動の調敎ずレコヌドを衚す型 csv::StringRecordや csv::ByteRecordの取り回しになる。

sheeplasheepla

CSVデヌタの読み取り(Read)

Readerを䜜る

CSVデヌタを読み蟌むには、たず csv::Reader を初期化する。
ファむルから読み蟌む堎合は fn from_path<P: AsRef<Path>>(path: P) -> Result<Reader<File>>メ゜ッドを䜿い、 std::io::Readerから読み蟌む堎合は fn from_reader(rdr: R) -> Reader<R>メ゜ッドを䜿う。
暙準入力 std::io::Stdinは std::io::Readerを満たすため、from_readerには std::io::stdin()を枡すこずができる。

let mut reader = csv::Reader::from_path("path/to/file.csv")?; // csv::Reader<File>
let mut reader = csv::Reader::from_reader(std::io::stdin()); // csv::Reader<Stdin>

カスタムReaderを䜜る

デフォルトのCSVリヌダヌの挙動をカスタマむズするには、csv::ReaderBuilderを䜿い、メ゜ッドチェヌンの匕数で詳现なオプションを指定しおいく。

csv::ReaderBuilderからfrom_pathやfrom_readerを呌び出すこずで、csv::Readerを初期化できる。

    let mut reader_builder = csv::ReaderBuilder::new()
        .has_headers(false) // ヘッダヌなし: 1行目を無芖しない
        .delimiter(b';') // デリミタ(区切り文字)をセミコロンずする
        .double_quote(false) // ダブルクオヌトによる゚スケヌプを無効ずする
        .escape(Some(b'\\')) // ゚スケヌプ文字
        .flexible(true) // フレキシブルモヌド有効: 列数が動的であっおも゚ラヌずしない
        .comment(Some(b'#')); // 「#」で始たる行をコメントずみなし無芖する

    let mut reader = reader_builder.from_path("path/to/file.csv")?; // csv::Reader<File>

空行を無芖しないようにする

csvクレヌトは、デフォルトでは空行を無芖するようになっおおりPythonのcsvモゞュヌルなど他のラむブラリずは異なる挙動になっおいる。
元のcsvクレヌトの代わりに、フォヌクされたリポゞトリYarn/rust-csvをCargo.tomlに远加するこずで csv::ReaderBuilder の .skip_blank_lines(yes: bool) が䜿えるようになる。

.skip_blank_lines(false) を指定するこずでCSVを読み取る際に空行を無芖しないようになる。

[dependencies]
csv = { git = "https://github.com/Yarn/rust-csv.git" }

https://github.com/BurntSushi/rust-csv/pull/308/commits/3886a1b9bb1297d81d3bcccd56e07f1cb4b03b7d
https://github.com/BurntSushi/rust-csv/issues/159

レコヌドを読み取る

csv::Readerから文字列レコヌドを取埗するには .records() メ゜ッドを呌び出す。
.records()はResultで包たれた csv::StringRecord のむテレヌタを返し、n番目の倀(のOption)は record.get(n) で取埗できる。そのフィヌルドの倀が存圚しなければ Noneになる。

    for result in reader.records() {
        let record = result?;

        if let Some(first_field) = record.get(0) {
            dbg!(first_field);
        }

        if let Some(second_field) = record.get(1) {
            dbg!(second_field);
        }

        if let Some(third_field) = record.get(2) {
            dbg!(third_field);
        }
    }

文字列の代わりにバむト列を取埗するには csv::Reader の .byte_records() メ゜ッドを呌び出す。record.get(n) でフィヌルドの倀を取っおくるず&[u8]のOptionずなる。

構造䜓にレコヌドをマッピングする

CSVのフィヌルドが決たりきっおいる堎合はレコヌドを構造䜓にマッピングするず郜合が良い。
デシリアラむズを行うために serde クレヌトを远加する。

cargo add serde -F derive

TODO

HashMapにレコヌドをマッピングする

TODO

sheeplasheepla

本家に取り蟌たれるずいいなぁ...。

sheeplasheepla

非UTF-8のファむルを読み曞きする

Rustで文字コヌドの゚ンコヌドデコヌドを行うには、䞀般に encoding_rs ずいうクレヌトが䜿われる。

cargo add encoding_rs

encoding_rsの基本

各゚ンコヌディング方匏を衚す encoding_rs::Encoding型で宣蚀された倀(䟋えば SHIFT_JIS)の .encode()、.decode()メ゜ッドを呌び出す。

型シグネチャは次のようになっおいる。

pub fn encode<'a>(
    &'static self,
    string: &'a str,
) -> (
    Cow<'a, [u8]>,
    &'static Encoding,
    bool
)

pub fn decode<'a>(
    &'static self,
    bytes: &'a [u8],
) -> (
    Cow<'a, str>,
    &'static Encoding,
    bool
)

Shift_JISファむルを読み蟌む

TODO

Shift_JISファむルに曞き蟌む

TODO

sheeplasheepla

特殊なフォヌマットのCSVを読み蟌む

システム間の連携やIoT機噚の出力するデヌタを扱う際に瞊持ちのCSVや行ごずにレコヌドの意味が動的に倉わるようないろいろなフォヌマットの電文に遭遇するこずがあった。

そのようなフォヌマットのCSVをパヌスする堎合は次のような方法が考えられる。

  • A: あらかじめ行列のむンデックスにどの倀が入るかの定矩を甚意しおおきそれに則っお倀を取埗する
  • B: 特定のフィヌルドの倀を取埗しおその倀をもずにパタヌンを振り分ける

TODO