1
+ // ANCHOR: word_counter
2
+ use std:: collections:: HashMap ;
3
+ use clap:: Parser ;
4
+
5
+ /// A word counting program
6
+ #[ derive( Parser ) ]
7
+ #[ command( author, version, about, long_about = None ) ]
8
+ struct Args {
9
+ /// Text to count words in
10
+ #[ arg( short, long) ]
11
+ text : Option < String > ,
12
+
13
+ /// File to read text from
14
+ #[ arg( short, long) ]
15
+ file : Option < String > ,
16
+
17
+ /// Ignore case when counting words
18
+ #[ arg( short, long, default_value_t = true ) ]
19
+ ignore_case : bool ,
20
+ }
21
+
22
+ /// WordCounter counts the frequency of words in text.
23
+ struct WordCounter {
24
+ word_counts : HashMap < String , usize > ,
25
+ ignore_case : bool ,
26
+ }
27
+
28
+ impl WordCounter {
29
+ /// Create a new WordCounter.
30
+ fn new ( ignore_case : bool ) -> Self {
31
+ WordCounter {
32
+ word_counts : HashMap :: new ( ) ,
33
+ ignore_case,
34
+ }
35
+ }
36
+
37
+ /// Count words in the given text.
38
+ fn count_words ( & mut self , text : & str ) {
39
+ for word in text. split_whitespace ( ) {
40
+ let word = if self . ignore_case {
41
+ word. to_lowercase ( )
42
+ } else {
43
+ word. to_string ( )
44
+ } ;
45
+ * self . word_counts . entry ( word) . or_insert ( 0 ) += 1 ;
46
+ }
47
+ }
48
+
49
+ /// Get the count for a specific word.
50
+ fn word_count ( & self , word : & str ) -> usize {
51
+ let word = if self . ignore_case {
52
+ word. to_lowercase ( )
53
+ } else {
54
+ word. to_string ( )
55
+ } ;
56
+ self . word_counts . get ( & word) . copied ( ) . unwrap_or ( 0 )
57
+ }
58
+
59
+ /// Find the most frequent word(s) and their count.
60
+ fn most_frequent ( & self ) -> Vec < ( & str , usize ) > {
61
+ if self . word_counts . is_empty ( ) {
62
+ return Vec :: new ( ) ;
63
+ }
64
+
65
+ let max_count = self . word_counts . values ( ) . max ( ) . unwrap ( ) ;
66
+ self . word_counts
67
+ . iter ( )
68
+ . filter ( |( _, & count) | count == * max_count)
69
+ . map ( |( word, & count) | ( word. as_str ( ) , count) )
70
+ . collect ( )
71
+ }
72
+
73
+ /// Print word counts in alphabetical order
74
+ fn print_counts ( & self ) {
75
+ let mut words: Vec < _ > = self . word_counts . keys ( ) . collect ( ) ;
76
+ words. sort ( ) ;
77
+ for word in words {
78
+ println ! ( "{}: {}" , word, self . word_counts[ word] ) ;
79
+ }
80
+ }
81
+ }
82
+ // ANCHOR_END: word_counter
83
+
84
+ // ANCHOR: tests
85
+ #[ test]
86
+ fn test_empty_counter ( ) {
87
+ let counter = WordCounter :: new ( true ) ;
88
+ assert_eq ! ( counter. word_count( "any" ) , 0 ) ;
89
+ assert ! ( counter. most_frequent( ) . is_empty( ) ) ;
90
+ }
91
+
92
+ #[ test]
93
+ fn test_simple_text ( ) {
94
+ let mut counter = WordCounter :: new ( true ) ;
95
+ counter. count_words ( "Hello world, hello Rust!" ) ;
96
+ assert_eq ! ( counter. word_count( "hello" ) , 2 ) ;
97
+ assert_eq ! ( counter. word_count( "rust" ) , 1 ) ;
98
+ assert_eq ! ( counter. word_count( "world" ) , 1 ) ;
99
+ }
100
+
101
+ #[ test]
102
+ fn test_case_insensitive ( ) {
103
+ let mut counter = WordCounter :: new ( true ) ;
104
+ counter. count_words ( "Hello HELLO hello" ) ;
105
+ assert_eq ! ( counter. word_count( "hello" ) , 3 ) ;
106
+ assert_eq ! ( counter. word_count( "HELLO" ) , 3 ) ;
107
+ }
108
+
109
+ #[ test]
110
+ fn test_most_frequent ( ) {
111
+ let mut counter = WordCounter :: new ( true ) ;
112
+ counter. count_words ( "hello world hello rust hello" ) ;
113
+ let most_frequent = counter. most_frequent ( ) ;
114
+ assert_eq ! ( most_frequent, vec![ ( "hello" , 3 ) ] ) ;
115
+ }
116
+ // ANCHOR_END: tests
117
+
118
+ fn main ( ) {
119
+ let args = Args :: parse ( ) ;
120
+
121
+ let mut counter = WordCounter :: new ( args. ignore_case ) ;
122
+
123
+ if let Some ( text) = args. text {
124
+ counter. count_words ( & text) ;
125
+ } else if let Some ( file) = args. file {
126
+ match std:: fs:: read_to_string ( file) {
127
+ Ok ( content) => counter. count_words ( & content) ,
128
+ Err ( e) => {
129
+ eprintln ! ( "Error reading file: {}" , e) ;
130
+ std:: process:: exit ( 1 ) ;
131
+ }
132
+ }
133
+ } else {
134
+ eprintln ! ( "Please provide either --text or --file" ) ;
135
+ std:: process:: exit ( 1 ) ;
136
+ }
137
+
138
+ println ! ( "Word counts:" ) ;
139
+ counter. print_counts ( ) ;
140
+ }
0 commit comments