Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

RUST ベクトル(構造体)のソート

うまくいってよかったね。僕は眠いから寝ますと言っている柴犬です。

概要

構造体 Sline を定義します。

この構造体をインスタンス化してCSVのデータをセットします。

ベクトル slines にデータをセットした構造体を追加します。

ソートに前回紹介した「O’reilly プログラミングRust第2版にある16.2.8 ソートと検索」にある次のコードを使ってみました。

students.sort_by(|a, b| {
    let a_key = (&a.last_name, &a.first_name);
    let b_key = (&b.last_name, &b.first_name);
    a_key.cmp(&b_key)
});

ソートの実行

これまでのコードからクイックソート部分を取り除き、構造体のベクトルを追加しました。

ソートは爆速になりました。体感的には3秒弱ほどでしたので、他の言語と同等になりました。ですから、私のクイックソートが悪かったという結果になりました。

書き出しを含めて全体で1分30秒ほど要していますが、ほとんどが書き出しの時間ということで、ここを最も改善したいところです。

構造体の定義

// 構造体の定義
struct Sline {
    f1: String,
    f2: String,
}

// 構造体のメソッド
impl Sline {
    fn new(f1: &str, f2: &str) -> Self {
        Self {
            f1: f1.to_string(),
            f2: f2.to_string(),
        }
    }
}

構造体を要素とするベクトルのソート

構造体に合わせます。

    slines.sort_by(|a, b| {
        let a_key = (&a.f1, &a.f2);
        let b_key = (&b.f1, &b.f2);
        a_key.cmp(&b_key)
    });

とりあえず実行したコード

copy

use encoding_rs::SHIFT_JIS;
use std::error::Error;
use std::fs;
use std::io::Write;

// 構造体の定義
struct Sline {
    f1: String,
    f2: String,
}

// 構造体のメソッド
impl Sline {
    fn new(f1: &str, f2: &str) -> Self {
        Self {
            f1: f1.to_string(),
            f2: f2.to_string(),
        }
    }
}

fn run() -> Result<(), Box<dyn Error>> {
    let path = "./input.csv";
    let buf = fs::read(path).unwrap();
    let (dec, _, _) = SHIFT_JIS.decode(&buf);
    let text = dec.into_owned();
    let lines = text.lines();

    // 空のベクトルのベクトルを作成
    let mut slines = Vec::new();
    for (_, s) in lines.enumerate() {
        if s.trim().len() == 0 {
            continue;
        }
        let fields: Vec<_> = s.split(",").map(|field| field.trim()).collect();
        // 構造体をベクトルに追加
        slines.push(Sline::new(fields[0], fields[1]));
    }

    // ここで使っています。
    slines.sort_by(|a, b| {
        let a_key = (&a.f1, &a.f2);
        let b_key = (&b.f1, &b.f2);
        a_key.cmp(&b_key)
    });

    let len = slines.len();
    let mut file = fs::File::create("./output.csv")?;

    // ここからが遅い
    let mut record = String::new();
    let mut count = 1;
    for x in slines {
        if count == len {
            record = format!("{}", record) + &x.f1 + "," + &x.f2;
        } else {
            record = format!("{}", record) + &x.f1 + "," + &x.f2 + "\n";
        }

        if count % 1000 == 0 {
            let (enc, _, _) = SHIFT_JIS.encode(&record);
            let output = enc.into_owned();
            file.write_all(&output)?;
            record = format!("{}", "");
        }

        count += 1;
    }
    let (enc, _, _) = SHIFT_JIS.encode(&record);
    let output = enc.into_owned();
    file.write_all(&output)?;

    file.flush()?;

    Ok(())
}

fn main() {
    if let Err(err) = run() {
        println!("{:?}", err);
    }
}

書き出しの検討

書き出しの部分を BufWriter を使うと早くなるという記事も見ましたのでこれを使ってみました。

上記のコードで変更は fn run() の次のところです。

use std::io::Write; も忘れずに変更します。

use std::io::Write;

    let len = slines.len();
    let mut file = fs::File::create("./output.csv")?;

    // ここからが遅い
    let mut record = String::new();
    let mut count = 1;
    for x in slines {
        if count == len {
            record = format!("{}", record) + &x.f1 + "," + &x.f2;
        } else {
            record = format!("{}", record) + &x.f1 + "," + &x.f2 + "\n";
        }

        if count % 1000 == 0 {
            let (enc, _, _) = SHIFT_JIS.encode(&record);
            let output = enc.into_owned();
            file.write_all(&output)?;
            record = format!("{}", "");
        }

        count += 1;
    }
    let (enc, _, _) = SHIFT_JIS.encode(&record);
    let output = enc.into_owned();
    file.write_all(&output)?;

    file.flush()?;

これを次のように変えています。

use std::io::{BufWriter, Write};
    
    let mut record = String::new();
    let mut bufw = BufWriter::new(fs::File::create("./output.csv").unwrap());
    for x in slines {
        record = format!("{}", &x.f1) + "," + &x.f2 + "\n";
        let (enc, _, _) = SHIFT_JIS.encode(&record);
        let output = enc.into_owned();
        bufw.write_all(&output).unwrap();
    }
    bufw.flush()?;

ちょっと期待しましたが、私のコードが悪いのかわかりませんが体感的に変わりはありませんでした。残念です。

fn run() の全体も掲載します。

copy

use encoding_rs::SHIFT_JIS;
use std::error::Error;
use std::fs;
use std::io::{BufWriter, Write};

fn run() -> Result<(), Box<dyn Error>> {
    let path = "./input.csv";
    let buf = fs::read(path).unwrap();
    let (dec, _, _) = SHIFT_JIS.decode(&buf);
    let text = dec.into_owned();
    let lines = text.lines();

    let mut slines = Vec::new();
    for (_, s) in lines.enumerate() {
        if s.trim().len() == 0 {
            continue;
        }

        let fields: Vec<_> = s.split(",").map(|field| field.trim()).collect();
        slines.push(Sline::new(fields[0], fields[1]));
    }

    slines.sort_by(|a, b| {
        let a_key = (&a.f1, &a.f2);
        let b_key = (&b.f1, &b.f2);
        a_key.cmp(&b_key)
    });

    // -----変更はここから
    let mut record = String::new();
    let mut bufw = BufWriter::new(fs::File::create("./output.csv").unwrap());
    for x in slines {
        record = format!("{}", &x.f1) + "," + &x.f2 + "\n";
        let (enc, _, _) = SHIFT_JIS.encode(&record);
        let output = enc.into_owned();
        bufw.write_all(&output).unwrap();
    }
    bufw.flush()?;
    // -----ここまで

    Ok(())
}