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