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

Rustで[言語処理100本ノック](http://www.cl.ecei.tohoku.ac.jp/nlp100/)しています。
# 第2章: UNIXコマンドの基礎
本章はUNIXコマンドの中でもファイル処理がメインですね。
[後編はこちら](https://qiita.com/kbone/items/36a48daf62a43f605e2d)です。

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

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

```rust
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' ' '`に相当するものですね。

```rust
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`に相当するものですね。
なんだか長くなってしまいました。`Option`と`Result`が混在している状態ではなかなかスッキリした記述が得られない気がします。もっと良い方法ないのでしょうか？

```rust
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`に相当するものですね。さっきとは打って変わってとてもスッキリ書けました。

```rust
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`に相当するものですね。先程以上にシンプルになりました。

```rust
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`すればいいかと思っていたら、`Lines`も`Take`も`DoubleEndedIterator`を実装していないので、一時的に`Vec`に変換する必要があるみたいです。

```rust
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()
}
```