Rustでforループの代わりにmapを使おうといろいろ試行錯誤した時のメモ

うさぎさんに少しでも追いつきたくて、コードを読んでるけどぜんぜん分からないのでメモ。

csv

これ。

Rust bookにはこんな感じの例が載ってる。

extern crate csv;
extern crate rustc_serialize;

use std::vec::Vec;
use std::path::Path;
use std::fs;

#[derive(Debug, RustcDecodable)]
struct Row {
    country: String,
    city: String,
    population: i32,
    gdp: f64
}

fn main() {
    let data_path = Path::new("path/to/file.csv");
    
    let file = fs::File::open(data_path).unwrap();
    let mut rdr = csv::Reader::from_reader(file);
    
    for row in rdr.decode::<Row>() {
        let row = row.unwrap();
    }
}

(https://doc.rust-lang.org/book/error-handling.html#writing-the-logic)

ここでは、decode()Rowのtype hintをつけることで、あらかじめ自分で定義したRowを使ってパターンマッチングができる。

impl<R: Read> Reader<R>
fn decode<'a, D: Decodable>(&'a mut self) -> DecodedRecords<'a, R, D>

DecodedRecordsにはmap()とかcollect()があるので、その辺でうまいことVec<f64>に変換して、forループをやめたい。

map()の中で副作用があることをする

追記:コメント欄で指摘をもらったのでcollect()を書き足しました。

extern crate csv;
use std::vec::Vec;

fn main() {
    let mut rdr = csv::Reader::from_string("a,b,c\n1,2,3\n4,5,6").has_headers(true);
    let mut x: Vec<f64> = vec![];

    for row in rdr.decode::<Vec<f64>>() {
        row.unwrap().iter().map(|&c| x.push(c)).collect::<Vec<_>>();
    }
    
    println!("{:?}", &x);
}

iter()の代わりにinto_iter()を使うと、こう。

        row.unwrap().into_iter().map(|c| x.push(c));
結果
$ ./target/debug/csv.exe
[1, 2, 3, 4, 5, 6]

雑な例

ドキュメントの例をなぞるとこんな感じのはず。とりあえずネストしたままのやつ。

extern crate csv;
use std::vec::Vec;

fn main() {
    let mut rdr = csv::Reader::from_string("a,b,c\n1,2,3\n4,5,6").has_headers(true);
    let x: Vec<Vec<f64>> = rdr.decode().collect::<csv::Result<Vec<Vec<f64>>>>().unwrap();
    
    println!("{:?}", x);
}
結果
$ ./target/debug/csv.exe
[[1, 2, 3], [4, 5, 6]]

Vec<Vec>をVecにする

fold()すればいけるかなと思ったんですが、extend()は元の値を返してくれないので、そんなことはできません。

    let x: Vec<Vec<f64>> = rdr.decode().collect::<csv::Result<Vec<Vec<f64>>>>().unwrap();
    let x: Vec<f64> = x.into_iter().fold(vec![], |v1, v2| v1.extend(v2.iter().cloned()));

よくわからないのですが、flat_map()というのを使うといいみたいです。 2回collect()してるのはなんかいまいちな感じしかしませんけど、これ以上いい書き方が思いつきません。。

extern crate csv;
use std::vec::Vec;

fn main() {
    let mut rdr = csv::Reader::from_string("a,b,c\n1,2,3\n4,5,6").has_headers(true);
    let x: Vec<Vec<f64>> = rdr.decode().collect::<csv::Result<Vec<Vec<f64>>>>().unwrap();
    
    let x: Vec<f64> = x
        .into_iter()
        .flat_map(|c| c)
        .collect();
    
    println!("{:?}", x);
}
結果
$ ./target/debug/csv.exe
[1, 2, 3, 4, 5, 6]

感想

Rustむずい。何より、ググった時にほとんど役立つ情報が出てこないのがつらい...。