Sibainu Relax Room

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

RUST ソートで遊ぶ

へぇーそうなんだと思っている柴犬

概要

前回の次のソート部分のコードを拡張すれば、降順、フィールドの入れ替えができるのではと思いましたので確かめてみます。

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

タプルの比較ができればいいようですので、2つの例で確かめます。

Shift-JIS のデコード・エンコードが高コストで、CSVファイルの入出力が UTF-8 ならかなり速いことも分かりました。

ソートの優先順位

優先順位を第一フィールド f1 を昇順、第三 f3 を昇順、第二 f2 を昇順にしてソートしてみます。

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

結果は下の画像のとおりです。ここで、一七が一三より先になっているのでおかしいと思われるかもしれませんが、辞書順は次のとおりですので正しい昇順であることを理解できると思います。

一 七 三 九 二 五 八 六 四

左がソート前、右がソートした結果です

降順にするには

優先順位を第三フィールド f3 を昇順、第一 f1 を降順、第二 f2 を降順にしてソートしてみます。

降順にするには、降順にしたいフィールドの a と b を入れ替えるだけです。

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

左がソート前、右がソートした結果です。

使用したコード

copy

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

// -----フィールドに応じて変えます。
struct Sline {
    f1: String,
    f2: String,
    f3: String,
}

impl Sline {
    fn new(f1: &str, f2: &str, f3: &str) -> Self {
        Self {
            f1: f1.to_string(),
            f2: f2.to_string(),
            f3: f3.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], fields[2]));
    }

    // -----ソート部分
    slines.sort_by(|a, b| {
        let a_key = (&a.f3, &b.f1, &b.f2);
        let b_key = (&b.f3, &a.f1, &a.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 + "," + &x.f3 + "\n";
        let (enc, _, _) = SHIFT_JIS.encode(&record);
        let output = enc.into_owned();
        bufw.write_all(&output).unwrap();
    }
    bufw.flush()?;

    Ok(())
}

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

結果

想定したとおりになりました。

ただ、ソートに要する時間に変化は感じられませんでしたが、書き出しに要した時間は倍の4分ほど要しました。

レコード数は同じなので、文字列の結合

record = format!("{}", &x.f1) + "," + &x.f2 + "," + &x.f3 + "\n";

にコストがかかっていると思われます。

書き出し部分をちょっと変えてみました

改めて、書き出しのところを次のようにして実行してみました。

当初、ファイル一括の文字列で行って完了しませんでしたが、今回は初めに大きな領域(1億バイト)を確保してファイル全体の文字列の結合を行うことにしました。

これで文字列の結合に要する時間を知ることができるようになり、その時間は1秒程でした。

完了まで全体で時間は2分40秒となり、差し引きShift_JIS のエンコードにほとんどの時間を要することが分かりました。これ以上探求することはやめます。

copy

    // -----1億バイトの領域を確保
    let mut record = String::with_capacity(100000000);
    for x in slines {
        // -----String の add 演算子なので x.f1 でなく &x.f1 となります。
        record = record + &x.f1 + "," + &x.f2 + "," + &x.f3 + "\n";
    }
    // -----ここに時間がかかっている
    let (enc, _, _) = SHIFT_JIS.encode(&record);
    let output = enc.into_owned();
    let mut bufw = BufWriter::new(fs::File::create("./output.csv").unwrap());
    bufw.write_all(&output).unwrap();
    bufw.flush()?;