1+ use std:: env;
2+ use std:: fs;
3+ use std:: io;
4+ use std:: process;
5+ use rand:: seq:: SliceRandom ;
6+ use rand:: thread_rng;
7+
8+ const MIN_SENTENCE_NUM : usize = 5 ;
9+ const MIN_USER_NUM : usize = 1 ;
10+ const SENTENCE_FILE : & str = "hsr.dat" ;
11+ const USER_FILE : & str = "usr.dat" ;
12+ const USER_TEMPLATE : & str = "${user}" ;
13+
14+ fn main ( ) {
15+ let args: Vec < String > = env:: args ( ) . collect ( ) ;
16+
17+ if args. len ( ) != 2 {
18+ eprintln ! ( "hhss: argc != 2." ) ;
19+ process:: exit ( 1 ) ;
20+ }
21+
22+ let num_sentences = match parse_num_sentences ( & args[ 1 ] ) {
23+ Ok ( n) => n,
24+ Err ( e) => {
25+ eprintln ! ( "hhss: {}" , e) ;
26+ process:: exit ( 1 ) ;
27+ }
28+ } ;
29+
30+ let sentences = match read_data_file ( SENTENCE_FILE ) {
31+ Ok ( lines) => lines,
32+ Err ( e) => {
33+ eprintln ! ( "hhss: failed to open {} as mode \" r\" : {}" , SENTENCE_FILE , e) ;
34+ process:: exit ( 1 ) ;
35+ }
36+ } ;
37+
38+ let users = match read_data_file ( USER_FILE ) {
39+ Ok ( lines) => lines,
40+ Err ( e) => {
41+ eprintln ! ( "hhss: failed to open {} as mode \" r\" : {}" , USER_FILE , e) ;
42+ process:: exit ( 1 ) ;
43+ }
44+ } ;
45+
46+ if sentences. len ( ) < MIN_SENTENCE_NUM {
47+ eprintln ! ( "hhss: it is required for {} to have valid lines more than or equal to {}." ,
48+ SENTENCE_FILE , MIN_SENTENCE_NUM ) ;
49+ process:: exit ( 1 ) ;
50+ }
51+
52+ if users. len ( ) < MIN_USER_NUM {
53+ eprintln ! ( "hhss: it is required for {} to have valid lines more than or equal to {}." ,
54+ USER_FILE , MIN_USER_NUM ) ;
55+ process:: exit ( 1 ) ;
56+ }
57+
58+ if num_sentences > sentences. len ( ) {
59+ eprintln ! ( "hhss: argv[1] = {} needs to be <= {}, the number of valid lines in {}." ,
60+ num_sentences, sentences. len( ) , SENTENCE_FILE ) ;
61+ process:: exit ( 1 ) ;
62+ }
63+
64+ let selected_sentences = select_random_sentences ( & sentences, num_sentences) ;
65+
66+ for sentence in selected_sentences {
67+ let processed_sentence = replace_templates ( & sentence, & users) ;
68+ println ! ( "{}" , processed_sentence) ;
69+ }
70+ }
71+
72+ fn parse_num_sentences ( arg : & str ) -> Result < usize , String > {
73+ match arg. parse :: < usize > ( ) {
74+ Ok ( n) => {
75+ if n < MIN_SENTENCE_NUM {
76+ Err ( format ! ( "argv[1] = {} needs to be >= {}." , n, MIN_SENTENCE_NUM ) )
77+ } else {
78+ Ok ( n)
79+ }
80+ }
81+ Err ( _) => Err ( "no conversion can be performed." . to_string ( ) )
82+ }
83+ }
84+
85+ fn read_data_file ( filename : & str ) -> Result < Vec < String > , io:: Error > {
86+ let contents = fs:: read_to_string ( filename) ?;
87+ let mut valid_lines = Vec :: new ( ) ;
88+
89+ for line in contents. lines ( ) {
90+ let trimmed = line. trim ( ) ;
91+ // Skip empty lines and comments
92+ if !trimmed. is_empty ( ) && !trimmed. starts_with ( '#' ) {
93+ valid_lines. push ( trimmed. to_string ( ) ) ;
94+ }
95+ }
96+
97+ Ok ( valid_lines)
98+ }
99+
100+ fn select_random_sentences ( sentences : & [ String ] , count : usize ) -> Vec < String > {
101+ let mut indices: Vec < usize > = ( 0 ..sentences. len ( ) ) . collect ( ) ;
102+ let mut rng = thread_rng ( ) ;
103+
104+ // Fisher-Yates shuffle
105+ indices. shuffle ( & mut rng) ;
106+
107+ // Take first 'count' indices and get corresponding sentences
108+ indices. into_iter ( )
109+ . take ( count)
110+ . map ( |i| sentences[ i] . clone ( ) )
111+ . collect ( )
112+ }
113+
114+ fn replace_templates ( sentence : & str , users : & [ String ] ) -> String {
115+ let mut result = String :: new ( ) ;
116+ let mut last_user: Option < String > = None ;
117+ let mut rng = thread_rng ( ) ;
118+
119+ let parts: Vec < & str > = sentence. split ( USER_TEMPLATE ) . collect ( ) ;
120+
121+ for ( i, part) in parts. iter ( ) . enumerate ( ) {
122+ result. push_str ( part) ;
123+
124+ // If this is not the last part, we need to add a user name
125+ if i < parts. len ( ) - 1 {
126+ let mut available_users = users. to_vec ( ) ;
127+
128+ // If we have more than one user and we used one before, remove it to avoid repetition
129+ if users. len ( ) > 1 {
130+ if let Some ( last) = & last_user {
131+ available_users. retain ( |u| u != last) ;
132+ }
133+ }
134+
135+ let selected_user = available_users. choose ( & mut rng) . unwrap ( ) ;
136+ result. push_str ( selected_user) ;
137+ last_user = Some ( selected_user. clone ( ) ) ;
138+ }
139+ }
140+
141+ result
142+ }
143+
144+ #[ cfg( test) ]
145+ mod tests {
146+ use super :: * ;
147+
148+ #[ test]
149+ fn test_parse_num_sentences ( ) {
150+ assert_eq ! ( parse_num_sentences( "5" ) . unwrap( ) , 5 ) ;
151+ assert_eq ! ( parse_num_sentences( "10" ) . unwrap( ) , 10 ) ;
152+ assert ! ( parse_num_sentences( "4" ) . is_err( ) ) ;
153+ assert ! ( parse_num_sentences( "abc" ) . is_err( ) ) ;
154+ }
155+
156+ #[ test]
157+ fn test_replace_templates ( ) {
158+ let users = vec ! [ "Alice" . to_string( ) , "Bob" . to_string( ) ] ;
159+ let sentence = "Hello ${user}, how are you ${user}?" ;
160+ let result = replace_templates ( sentence, & users) ;
161+
162+ // Should contain both users and they should be different
163+ assert ! ( result. contains( "Alice" ) || result. contains( "Bob" ) ) ;
164+ assert ! ( result. contains( "Hello" ) ) ;
165+ assert ! ( result. contains( "how are you" ) ) ;
166+ assert ! ( !result. contains( "${user}" ) ) ;
167+ }
168+
169+ #[ test]
170+ fn test_select_random_sentences ( ) {
171+ let sentences = vec ! [
172+ "First" . to_string( ) ,
173+ "Second" . to_string( ) ,
174+ "Third" . to_string( ) ,
175+ "Fourth" . to_string( ) ,
176+ "Fifth" . to_string( ) ,
177+ ] ;
178+
179+ let selected = select_random_sentences ( & sentences, 3 ) ;
180+ assert_eq ! ( selected. len( ) , 3 ) ;
181+
182+ // All selected sentences should be from the original list
183+ for sentence in & selected {
184+ assert ! ( sentences. contains( sentence) ) ;
185+ }
186+ }
187+ }
0 commit comments