LoginSignup
3
1

More than 5 years have passed since last update.

なぜかRustで言語処理100本ノック ~第2章 前編~

Last updated at Posted at 2018-10-17

Rustで言語処理100本ノックしています。

第2章: UNIXコマンドの基礎

本章はUNIXコマンドの中でもファイル処理がメインですね。
後編はこちらです。

10. 行数のカウント

行数をカウントせよ.確認にはwcコマンドを用いよ.

wc -l $FILEに相当するものですね。最初はlines().size_hint()でいけるかと思ったのですが、どうやらそうではないらしいので素直に実装しました。

pub fn count_lines(path: &Path) -> Result<usize> {
    let file = File::open(path)?;
    let br = BufReader::new(file);
    let mut counter = 0;
    br.lines().for_each(|_| counter += 1);
    Ok(counter)
}

解答に直接は関係ないですが、std::env::current_dir()std::pathとかに入っていないのがやや不思議に思います。

11. タブをスペースに置換

タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.

cat hightemp.txt | tr '\t' ' 'に相当するものですね。

pub fn tab_to_space(path: &Path, tab_width: usize) -> Result<String> {
    let file = File::open(path)?;
    let br = BufReader::new(file);
    let spaces = " ".repeat(tab_width);
    Ok(br.lines().map(|s| match s { Ok(s) => s.replace("\t", &spaces) + "\n", Err(_) => "\0".to_string() }).collect())
}

なぜlines().map()するとResultが返ってくるんでしょう?Errになる場合がパッとは浮かばないのですが。
[追記]コメントで教えていただいたのですが、UTF-8じゃない文字列を読み込ませようとするとErrが返るみたいです。ということは、標準ライブラリでnon UTF-8な文字列を扱おうとするのはやめたほうが良さそうですね。
[追々記]これもコメントで教えていただいたのですが、内部でIOしているからというのもあるようです。皆さん教えていただきありがとうございます。

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.

cut --field=$N hightemp.txt > col$N.txtに相当するものですね。
なんだか長くなってしまいました。OptionResultが混在している状態ではなかなかスッキリした記述が得られない気がします。もっと良い方法ないのでしょうか?

pub fn get_col(source: &Path, out: &Path, column_number: usize) -> Result<()> {
    let source = File::open(source)?;
    let out = OpenOptions::new().write(true).create(true).truncate(true).open(out)?;
    let br = BufReader::new(source);
    let mut bw = BufWriter::new(out);
    br.lines().map(|line| {
        if let Ok(line) = line {
            match line.split_whitespace().nth(column_number) {
                Some(word) => Ok(word.to_string() + "\n"),
                None => Err(Error::new(ErrorKind::NotFound, format!("the column {} is not found.", column_number)))
            }
        } else {
            line
        }
    }).for_each(|col| { let _ = col.and_then(|col| bw.write(col.as_bytes())); });
    Ok(())
}

13. col1.txtとcol2.txtをマージ

12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ.

paste col1.txt col2.txtに相当するものですね。さっきとは打って変わってとてもスッキリ書けました。

pub fn merge_columns(source1: &Path, source2: &Path) -> Result<String> {
    let source1 = File::open(source1)?;
    let source2 = File::open(source2)?;
    let br1 = BufReader::new(source1);
    let br2 = BufReader::new(source2);
    Ok(br1.lines().zip(br2.lines()).map(|(col1, col2)| col1.unwrap() + "\t" + &col2.unwrap() + "\n").collect())
}

14. 先頭からN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.

head -$Nに相当するものですね。先程以上にシンプルになりました。

pub fn heads(path: &Path, n: usize) -> Result<String> {
    let file = File::open(path)?;
    let br = BufReader::new(file);
    br.lines().take(n).map(|line| line.and_then(|line| Ok(line + "\n"))).collect()
}

15. 末尾のN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.

tail -$Nに相当するものですね。単純にrevすればいいかと思っていたら、LinesTakeDoubleEndedIteratorを実装していないので、一時的にVecに変換する必要があるみたいです。

pub fn tails(path: &Path, n: usize) -> Result<String> {
    let file = File::open(path)?;
    let br = BufReader::new(file);
    let lines = br.lines().collect::<Vec<_>>();
    let n_lines = lines.into_iter().rev().take(n).collect::<Vec<_>>();
    n_lines.into_iter().rev().map(|line| line.and_then(|line| Ok(line + "\n"))).collect()
}
3
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1