bicycle_book_wordcount/
lib.rs

1//! `bicycle_book_wordcount` はシンプルな文字、単語、行の出現頻度の計数機能を提供します。
2//! 詳しくは[`count`](fn.count.html)関数のドキュメントを見て下さい。
3#![warn(missing_docs)]
4
5use regex::Regex;
6use std::collections::HashMap;
7use std::io::BufRead;
8
9/// [`count`](fn.count.html)で使うオプション
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum CountOption {
12    /// 文字毎に頻度を数える
13    Char,
14    /// 単語毎に頻度を数える
15    Word,
16    /// 行毎に頻度を数える
17    Line,
18}
19
20/// オプションのデフォルトは [`Word`](enum.CountOption.html#variant.Word)
21impl Default for CountOption {
22    fn default() -> Self {
23        CountOption::Word
24    }
25}
26
27/// `input` から1行ずつUTF-8文字列を読み込み、頻度を数える。
28///
29/// 頻度を数える対象はオプションによって制御される。
30/// * [`CountOption::Char`](enum.CountOption.html#variant.Char): Unicodeの1文字毎に頻度を数える
31/// * [`CountOption::Word`](enum.CountOption.html#variant.Word): 正規表現 `\w+` にマッチする単語毎に頻度を数える
32/// * [`CountOption::Line`](enum.CountOption.html#variant.Line): `\n`または`\r\n` で区切られた1行毎に頻度を数える
33///
34/// # Examples
35/// 入力中の単語の出現頻度を数える例
36///
37/// ```
38/// use std::io::Cursor;
39/// use bicycle_book_wordcount::{count, CountOption};
40///
41/// let mut input = Cursor::new("aa bb cc bb");
42/// let freq = count(input, CountOption::Word);
43///
44/// assert_eq!(freq["aa"], 1);
45/// assert_eq!(freq["bb"], 2);
46/// assert_eq!(freq["cc"], 1);
47/// ```
48///
49/// # Panics
50///
51/// 入力がUTF-8でフォーマットされていない場合にパニックする。
52pub fn count(input: impl BufRead, option: CountOption) -> HashMap<String, usize> {
53    let re = Regex::new(r"\w+").unwrap();
54    let mut freqs = HashMap::new(); // HashMap<String, usize>型
55
56    for line in input.lines() {
57        let line = line.unwrap();
58        use crate::CountOption::*;
59        match option {
60            Char => {
61                for c in line.chars() {
62                    *freqs.entry(c.to_string()).or_insert(0) += 1;
63                }
64            }
65            Word => {
66                for m in re.find_iter(&line) {
67                    let word = m.as_str().to_string();
68                    *freqs.entry(word).or_insert(0) += 1;
69                }
70            }
71            Line => *freqs.entry(line.to_string()).or_insert(0) += 1,
72        }
73    }
74    freqs
75}
76
77#[test]
78fn word_count_works() {
79    use std::io::Cursor;
80
81    let mut exp = HashMap::new();
82    exp.insert("aa".to_string(), 1);
83    exp.insert("bb".to_string(), 2);
84    exp.insert("cc".to_string(), 1);
85
86    assert_eq!(count(Cursor::new("aa bb cc bb"), CountOption::Word), exp);
87}
88
89#[test]
90fn word_count_works2() {
91    use std::io::Cursor;
92
93    let mut exp = HashMap::new();
94    exp.insert("aa".to_string(), 1);
95    exp.insert("cc".to_string(), 1);
96    exp.insert("dd".to_string(), 1);
97
98    assert_eq!(count(Cursor::new("aa  cc dd"), CountOption::Word), exp);
99}
100
101#[test]
102#[should_panic]
103fn word_count_do_not_contain_unknown_words() {
104    use std::io::Cursor;
105
106    count(
107        Cursor::new([
108            b'a', // a
109            0xf0, 0x90, 0x80, // でたらめなバイト列
110            0xe3, 0x81, 0x82, // あ
111        ]),
112        CountOption::Word,
113    );
114}
115
116#[test]
117fn word_count_works3() {
118    use std::io::Cursor;
119
120    let freqs = count(Cursor::new("aa  cc dd"), CountOption::Word);
121
122    assert_eq!(freqs.len(), 3);
123    assert_eq!(freqs["aa"], 1);
124    assert_eq!(freqs["cc"], 1);
125    assert_eq!(freqs["dd"], 1);
126}
127
128#[cfg(test)]
129mod test {
130    use super::*;
131    use std::io::Cursor;
132
133    macro_rules! assert_map {
134        ($expr: expr, {$($key: expr => $value:expr),*}) => {
135            $(assert_eq!($expr[$key], $value));*
136        };
137    }
138
139    #[test]
140    fn word_count_works3() {
141        let freqs = count(Cursor::new("aa  cc dd"), CountOption::Word);
142
143        assert_eq!(freqs.len(), 3);
144        assert_map!(freqs, {"aa" => 1, "cc" => 1, "dd" => 1});
145    }
146
147}