1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! wordcountは文字, 単語, 行の出現頻度の計算機能を提供する
//! 詳しくは[`count`](fn.count.html)関数のドキュメントを見てください
#![warn(missing_docs)]

use regex::Regex;
use std::collections::HashMap;
use std::io::BufRead;

/// [`count`](fn.count.html)で使うオプション
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CountOption {
    Char,
    Word,
    Line,
}

///オプションのデフォルトは[`Word`](enum.CountOption.html#vaiant.Word)
impl Default for CountOption {
    fn default() -> Self {
        CountOption::Word
    }
}

/// inputから一行ずつUTF-8文字列を読み込み, 頻度を数える
/// 
/// 頻度を数える対象はオプションによって制御される
/// * [`CountOption::Char`](enum.CountOption.html#variant.Char): Unicodeの一文字ごと
/// * [`CountOption::Word`](enum.CountOption.html#variant.Word): 正規表現\w+にマッチする単語ごと
/// * [`CountOption::Line`](enum.CountOption.html#variant.Line): \nまたは\r\nで区切られた一行ごと
/// 
/// # Panic
/// 
/// 入力がUTF-8出ない場合にPanicする.
pub fn count(input: impl BufRead, option: CountOption) -> HashMap<String, usize> {
    let re = Regex::new(r"\w+").unwrap();
    let mut freqs = HashMap::new();

    for line in input.lines(){
        let line = line.unwrap();

        use crate::CountOption::*;
        // 行を単語に分割する
        match option {
            Char => {
                for c in line.chars(){
                    *freqs.entry(c.to_string()).or_insert(0) += 1;
                }
            }
            Word => {
                for m in re.find_iter(&line) {
                    let word = m.as_str().to_string();
                    // 出現した単語の出現頻度を数える
                    *freqs.entry(word).or_insert(0) += 1;
                }
            }
            Line => *freqs.entry(line.to_string()).or_insert(0) += 1,
        }
    }
    freqs
}

#[test]
fn word_count_works() {
    use std::io::Cursor;

    let mut exp = HashMap::new();
    exp.insert("aa".to_string(), 1);
    exp.insert("bb".to_string(), 2);
    exp.insert("cc".to_string(), 1);

    assert_eq!(count(Cursor::new("aa bb cc bb"), CountOption::Word), exp);
}

#[test]
fn word_count_works2() {
    use std::io::Cursor;
    let mut exp = HashMap::new();
    exp.insert("aa".to_string(), 1);
    exp.insert("bb".to_string(), 1);
    exp.insert("dd".to_string(), 1);

    assert_eq!(count(Cursor::new("aa bb dd"), CountOption::Word), exp);
}

macro_rules!  assert_map {
    ($expr: expr, {$($key: expr => $value:expr), *}) => {
        $(assert_eq!($expr[$key], $value));*
    };
}

#[test]
fn word_count_works3() {
    use std::io::Cursor;

    let freqs = count(Cursor::new("aa cc dd"), CountOption::Word);

    assert_eq!(freqs.len(), 3);
    assert_map!(freqs, {"aa" => 1, "cc" => 1, "dd" => 1});
}

#[test]
#[should_panic]
fn word_count_do_not_contain_unkown_words() {
    use std::io::Cursor;

    count(
        Cursor::new([
            b'a', // a
            0xf0, 0x90, 0x80,
            0xe3, 0x81, 0x82,
        ]),
        CountOption::Word,
    );
}