11use rand:: { prelude:: SliceRandom , thread_rng} ;
2+ use std:: collections:: HashMap ;
23use std:: iter:: zip;
34use std:: ops:: { Add , AddAssign } ;
45
@@ -7,14 +8,14 @@ type PosVec = Vec<PosTuple>;
78type PosSlice < ' a > = & ' a [ PosTuple ] ;
89
910struct GuessResult {
10- non_partipating : Vec < char > ,
11+ max_participating : HashMap < char , usize > ,
1112 misplaced : PosVec ,
1213 correct : PosVec ,
1314}
1415
1516impl AddAssign for GuessResult {
1617 fn add_assign ( & mut self , other : Self ) {
17- self . non_partipating . extend ( other. non_partipating ) ;
18+ self . max_participating . extend ( other. max_participating ) ;
1819 self . misplaced . extend ( other. misplaced ) ;
1920 self . correct . extend ( other. correct ) ;
2021 }
@@ -28,10 +29,6 @@ impl Add for GuessResult {
2829 }
2930}
3031
31- fn contains_any ( word : & str , chars : & [ char ] ) -> bool {
32- chars. iter ( ) . any ( |c| word. contains ( * c) )
33- }
34-
3532fn contains_all ( word : & str , chars : & [ char ] ) -> bool {
3633 chars. iter ( ) . all ( |c| word. contains ( * c) )
3734}
@@ -49,6 +46,32 @@ fn contains_at_all(word: &str, pos: PosSlice) -> bool {
4946 . all ( |b| b)
5047}
5148
49+ fn get_letter_counts ( word : & str ) -> HashMap < char , usize > {
50+ let mut counts: HashMap < char , usize > = HashMap :: new ( ) ;
51+
52+ for c in word. chars ( ) {
53+ * counts. entry ( c) . or_insert ( 0 ) += 1 ;
54+ }
55+ counts
56+ }
57+
58+ fn contains_no_more_than ( word : & str , max_counts : & HashMap < char , usize > ) -> bool {
59+ let word_counts = get_letter_counts ( word) ;
60+
61+ !max_counts. iter ( ) . any ( |( c, max_count) | {
62+ // Check to see if the word has more than the max number of each letter
63+ let word_count_opt = word_counts. get ( c) ;
64+
65+ if let Some ( word_count) = word_count_opt {
66+ // Check if the word contains more than allowed max of the char
67+ word_count > max_count
68+ } else {
69+ // If the word doesn't contain the letter at all, we're okay.
70+ false
71+ }
72+ } )
73+ }
74+
5275fn contains_at_any ( word : & str , pos : PosSlice ) -> bool {
5376 pos. iter ( )
5477 . map ( |( val, pos) | contains_at ( word, * val, * pos) )
@@ -60,18 +83,36 @@ fn remaining_wordles_words(word_list: &[String], t: &GuessResult) -> Vec<String>
6083
6184 word_list
6285 . iter ( )
63- . filter ( |word| ! contains_any ( word, & t. non_partipating ) )
86+ . filter ( |word| contains_no_more_than ( word, & t. max_participating ) )
6487 . filter ( |word| contains_all ( word, & misplaced_positions_chars) )
6588 . filter ( |word| !contains_at_any ( word, & t. misplaced ) )
6689 . filter ( |word| contains_at_all ( word, & t. correct ) )
6790 . cloned ( )
6891 . collect ( )
6992}
7093
71- fn get_non_participating_chars ( solution : & str , guess : & str ) -> Vec < char > {
72- // For the provided solution and guess: Returns all the characters contained
73- // in guess which do not occur in solution.
74- guess. chars ( ) . filter ( |c| !solution. contains ( * c) ) . collect ( )
94+ fn get_max_char_counts ( solution : & str , guess : & str ) -> HashMap < char , usize > {
95+ // If the guess has more instances of a character than the solution has, we
96+ // can prune any word which has more instances of the character than the
97+ // solution. This function produces a map which contains this list.
98+
99+ let solution_counts = get_letter_counts ( solution) ;
100+ let guess_counts = get_letter_counts ( guess) ;
101+
102+ let mut char_counts: HashMap < char , usize > = HashMap :: new ( ) ;
103+
104+ for ( c, guess_count) in guess_counts. iter ( ) {
105+ let solution_count_opt = solution_counts. get ( c) ;
106+
107+ if let Some ( solution_count) = solution_count_opt {
108+ if * guess_count > * solution_count {
109+ char_counts. insert ( * c, * solution_count) ;
110+ }
111+ } else {
112+ char_counts. insert ( * c, 0 ) ;
113+ }
114+ }
115+ char_counts
75116}
76117
77118fn get_correct_chars ( solution : & str , guess : & str ) -> PosVec {
@@ -106,7 +147,7 @@ fn get_misplaced_chars(solution: &str, guess: &str) -> PosVec {
106147
107148fn get_all ( solution : & str , guess : & str ) -> GuessResult {
108149 GuessResult {
109- non_partipating : get_non_participating_chars ( solution, guess) ,
150+ max_participating : get_max_char_counts ( solution, guess) ,
110151 misplaced : get_misplaced_chars ( solution, guess) ,
111152 correct : get_correct_chars ( solution, guess) ,
112153 }
@@ -124,13 +165,12 @@ pub fn last_words_mr_bond(
124165 . map ( |guess| get_all ( & solution. to_lowercase ( ) , & guess. to_lowercase ( ) ) )
125166 . fold (
126167 GuessResult {
127- non_partipating : vec ! [ ] ,
168+ max_participating : HashMap :: new ( ) ,
128169 misplaced : vec ! [ ] ,
129170 correct : vec ! [ ] ,
130171 } ,
131172 |a, b| a + b,
132173 ) ;
133-
134174 remaining_wordles_words ( word_list, & guess_result)
135175 . choose_multiple ( & mut thread_rng ( ) , sample_size)
136176 . cloned ( )
@@ -214,9 +254,47 @@ mod tests {
214254 }
215255
216256 #[ test]
217- fn test_contains_any ( ) {
218- assert ! ( !contains_any( "eeuib" , & str2vec( "asdf" ) ) ) ;
219- assert ! ( contains_any( "eeaib" , & str2vec( "asdf" ) ) ) ;
257+ fn test_contains_no_more_than ( ) {
258+ // eerie contains no more than 3 e
259+ assert ! ( contains_no_more_than( "eerie" , & HashMap :: from( [ ( 'e' , 3 ) ] ) ) ) ;
260+ // eerie contains no more than 2 r
261+ assert ! ( contains_no_more_than( "eerie" , & HashMap :: from( [ ( 'r' , 2 ) ] ) ) ) ;
262+ // eerie contains no more than 1 q
263+ assert ! ( contains_no_more_than( "eerie" , & HashMap :: from( [ ( 'q' , 1 ) ] ) ) ) ;
264+ // eerie contains more than 2 e
265+ assert ! ( !contains_no_more_than( "eerie" , & HashMap :: from( [ ( 'e' , 2 ) ] ) ) ) ;
266+ // eerie contains more than 1 e
267+ assert ! ( !contains_no_more_than( "eerie" , & HashMap :: from( [ ( 'e' , 1 ) ] ) ) ) ;
268+ // eerie contains more than 0 r
269+ assert ! ( !contains_no_more_than( "eerie" , & HashMap :: from( [ ( 'r' , 0 ) ] ) ) ) ;
270+ }
271+
272+ #[ test]
273+ fn test_get_max_char_counts ( ) {
274+ // The guess doesn't reveal more e's than the solution, so we can't
275+ // infer a max count
276+ assert_eq ! ( get_max_char_counts( "eerie" , "e" ) , HashMap :: from( [ ] ) ) ;
277+ // The guess still doesn't reveal more e's than the solution, so we
278+ // can't infer a max count
279+ assert_eq ! ( get_max_char_counts( "eerie" , "ee" ) , HashMap :: from( [ ] ) ) ;
280+
281+ // The guess still doesn't reveal more e's than the solution, so we
282+ // can't infer a max count
283+ assert_eq ! ( get_max_char_counts( "eerie" , "eee" ) , HashMap :: from( [ ] ) ) ;
284+
285+ // The guess reveals more e's than the solution, so we can infer that
286+ // there is at most 3 e's
287+ assert_eq ! (
288+ get_max_char_counts( "eerie" , "eeee" ) ,
289+ HashMap :: from( [ ( 'e' , 3 ) ] )
290+ ) ;
291+
292+ // The guess reveals more e's than the solution, so we can infer that
293+ // there is at most 3 e's.
294+ assert_eq ! (
295+ get_max_char_counts( "poops" , "pppooos" ) ,
296+ HashMap :: from( [ ( 'p' , 2 ) , ( 'o' , 2 ) ] )
297+ ) ;
220298 }
221299
222300 #[ test]
@@ -264,7 +342,7 @@ mod tests {
264342 remaining_wordles_words(
265343 & WORDS ,
266344 & GuessResult {
267- non_partipating : str2vec ( "tread" ) ,
345+ max_participating : non_participating_count ( "tread" ) ,
268346 misplaced: vec!( ) ,
269347 correct: vec!( )
270348 }
@@ -276,7 +354,7 @@ mod tests {
276354 remaining_wordles_words(
277355 & WORDS ,
278356 & GuessResult {
279- non_partipating : str2vec ( "treadbo" ) ,
357+ max_participating : non_participating_count ( "treadbo" ) ,
280358 misplaced: vec!( ( 's' , 4 ) ) ,
281359 correct: vec!( ( 'i' , 2 ) , ( 'l' , 3 ) )
282360 }
@@ -288,19 +366,24 @@ mod tests {
288366 remaining_wordles_words(
289367 & WORDS ,
290368 & GuessResult {
291- non_partipating : str2vec ( "treadbok" ) ,
369+ max_participating : non_participating_count ( "treadbok" ) ,
292370 misplaced: vec!( ( 's' , 4 ) ) ,
293371 correct: vec!( ( 'i' , 2 ) , ( 'l' , 3 ) , ( 's' , 0 ) )
294372 }
295373 )
296374 . len( ) ,
297375 5
298- ) ;
376+ )
299377 }
300378
301379 #[ test]
302- fn test_get_non_participating_chars ( ) {
303- assert_eq ! ( get_non_participating_chars( "asdf" , "abcd" ) , vec!( 'b' , 'c' ) ) ;
380+ fn test_last_words_mr_bond ( ) {
381+ let v: Vec < String > = vec ! [ "purge" . to_string( ) , "puree" . to_string( ) ] ;
382+
383+ assert_eq ! (
384+ last_words_mr_bond( & v, "purge" , vec!( "pence" . to_string( ) ) , 5 ) ,
385+ vec!( "purge" )
386+ ) ;
304387 }
305388
306389 #[ test]
@@ -400,4 +483,13 @@ mod tests {
400483 fn str2vec ( str : & str ) -> Vec < char > {
401484 Vec :: from_iter ( str. chars ( ) )
402485 }
486+
487+ fn non_participating_count ( str : & str ) -> HashMap < char , usize > {
488+ let mut counts: HashMap < char , usize > = HashMap :: new ( ) ;
489+
490+ for c in str. chars ( ) {
491+ counts. insert ( c, 0 ) ;
492+ }
493+ counts
494+ }
403495}
0 commit comments