第2章: UNIXコマンドの基礎
16. ファイルをN分割する
自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.
split -l $N hightemp.txt
相当なのですが、このコマンドだとファイルが自動生成されるのに対して、今回はVec<String>
で返すようにしました(実質等価なので許してください)。スライスを用いればjoin
が使えるんですね。
pub fn split_file(path: &Path, n: usize) -> Result<Vec<String>> {
let file = File::open(path)?;
let br = BufReader::new(file);
let lines = br.lines().collect::<Result<Vec<_>>>();
lines.and_then(|lines| Ok(lines.chunks(n).map(|chunk| chunk.join("\n")).collect()))
}
17. 1列目の文字列の異なり
1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはsort, uniqコマンドを用いよ.
cat col1.txt | sort | uniq
これと同じ結果になれば良いわけですね。unwrap
しないように書いたら結構な長さになってしまいました。
pub fn get_column_differences(path: &Path, n: usize) -> Result<HashSet<String>> {
let file = File::open(path)?;
let br = BufReader::new(file);
let mut result = HashSet::new();
br.lines().map(|line|
line.and_then(|line|
line.split_whitespace().map(|word| word.to_string()).nth(n)
.ok_or(Error::new(ErrorKind::NotFound, format!("the column {} is not found.", n)))))
.for_each(|line| match line {
Ok(line) => { result.insert(line.to_string()); },
Err(e) => eprintln!("{}", e)
});
Ok(result)
}
18. 各行を3コラム目の数値の降順にソート
各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).
ファイル内容を見ると実は反転させるだけで結果が一致してしまうのですが、それはそれとして。こまんどのばあいとことなることがあるのは、安定ソートかどうかで結果が少し変わってしまうからですね。一応、相当するコマンドはsort hightemp.txt --key=3
です。
pub fn sort_by_column(path: &Path, n: usize) -> Result<Vec<String>> {
let file = File::open(path)?;
let br = BufReader::new(file);
let lines = br.lines().collect::<Result<Vec<_>>>();
lines.and_then(|mut lines| {
lines.sort_by(|a, b| a.split_whitespace().nth(n).cmp(&b.split_whitespace().nth(n)));
Ok(lines)
})
}
19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.
cut --field=1 hightemp.txt | sort | uniq -c | sort -r
相当なのですが、今回HashMap
を使った影響で同数だと順序不定になるようです。
pub fn sort_by_frequency(path: &Path) -> Result<Vec<String>> {
let file = File::open(path)?;
let br = BufReader::new(file);
let lines = br.lines().collect::<Result<Vec<_>>>();
let mut counter: HashMap<String, usize> = HashMap::new();
lines.and_then(|lines|
Ok(lines.into_iter().for_each(|line| {
let key = line.split_whitespace().next().unwrap_or("");
if counter.contains_key(key) {
*counter.get_mut(key).unwrap() += 1
} else {
counter.insert(key.to_string(), 1);
}
}))).and_then(|_| {
let mut tmp = counter.into_iter().collect::<Vec<_>>();
tmp.sort_by(|a, b| b.1.cmp(&a.1));
Ok(tmp.into_iter().map(|(k, _)| k).collect())
})
}
HashMap
の更新ってもう少しスッキリ書けないものかと思ってしまいました。
第2章の感想
UNIXコマンドの中でもファイル関係を扱う影響で、またできる限りunwrap
しないようにした結果、Result
及びOption
の扱い方が自然とわかってきました。この章をやっただけでも十分な収穫な気がします。