lib_wpsr/
word_filters.rs

1//! Prepares a list of words from a file for use in by the solver.
2
3use std::collections::HashMap;
4
5pub trait WordFilters {
6    fn filter_to_minimum_length(self, length: usize) -> Self;
7    fn filter_no_repeated_letters(&mut self) -> &mut Self;
8    fn filter_excludes_letters(self, exclude: &str) -> Self;
9    fn filter_includes_only_letters(&mut self, include: &str) -> &mut Self;
10    fn filter_includes_any_letters(self, include: &str) -> Self;
11    fn filter_includes_all_letters(self, include: &str) -> Self;
12    fn filter_includes_same_letters(&mut self, include: &str) -> &mut Self;
13    fn filter_includes_specific_letters_in_volume(self, include: &str) -> Self;
14}
15
16impl WordFilters for Vec<String> {
17    #[tracing::instrument(skip(self))]
18    fn filter_to_minimum_length(self, length: usize) -> Self {
19        let mut lc = self
20            .iter()
21            .map(|word| word.to_lowercase())
22            .collect::<Vec<String>>();
23        lc.retain(|word| word.len() >= length);
24
25        tracing::info!("Filtered to {} words", lc.len());
26        lc
27    }
28
29    #[tracing::instrument(skip(self))]
30    fn filter_no_repeated_letters(&mut self) -> &mut Self {
31        self.retain(|word| {
32            let mut chars = word.chars();
33            let mut a = chars.next().unwrap();
34            for b in chars {
35                if a == b {
36                    return false;
37                }
38                a = b
39            }
40            true
41        });
42
43        tracing::info!("With no repeated letters there are {} words", self.len());
44        self
45    }
46
47    #[tracing::instrument(skip(self))]
48    fn filter_excludes_letters(self, exclude: &str) -> Self {
49        let excludes = self
50            .iter()
51            .filter(|word| {
52                let chars = word.chars();
53                for char in chars {
54                    if exclude.contains(char) {
55                        return false;
56                    }
57                }
58                true
59            })
60            .map(|word| word.to_string())
61            .collect::<Vec<String>>();
62
63        tracing::info!(
64            "With no letters from the exclusion list ({})there are {} words",
65            exclude,
66            excludes.len()
67        );
68        excludes
69    }
70
71    #[tracing::instrument(skip(self))]
72    fn filter_includes_only_letters(&mut self, include: &str) -> &mut Self {
73        self.retain(|word| {
74            let chars = word.chars();
75            for char in chars {
76                if !include.contains(char) && char != ' ' {
77                    return false;
78                }
79            }
80            true
81        });
82
83        tracing::info!(
84            "With no letters from the exclusion list ({})there are {} words",
85            include,
86            self.len()
87        );
88        self
89    }
90
91    #[tracing::instrument(skip(self, include))]
92    fn filter_includes_all_letters(self, include: &str) -> Self {
93        let includes = self
94            .iter()
95            .filter(|word| {
96                let chars = include.chars();
97                for char in chars {
98                    if !word.contains(char) {
99                        return false;
100                    }
101                }
102                true
103            })
104            .map(|word| word.to_string())
105            .collect::<Vec<String>>();
106
107        tracing::info!(
108            "With no letters from the exclusion list ({})there are {} words",
109            include,
110            includes.len()
111        );
112        includes
113    }
114
115    #[tracing::instrument(skip(self, include))]
116    fn filter_includes_any_letters(self, include: &str) -> Self {
117        let includes = self
118            .iter()
119            .filter(|word| {
120                let chars = word.chars();
121                for char in chars {
122                    if include.contains(char) {
123                        return true;
124                    }
125                }
126                false
127            })
128            .map(|word| word.to_string())
129            .collect::<Vec<String>>();
130
131        tracing::info!(
132            "With no letters from the exclusion list ({})there are {} words",
133            include,
134            includes.len()
135        );
136        includes
137    }
138
139    #[tracing::instrument(skip(self, anagram))]
140    fn filter_includes_same_letters(&mut self, anagram: &str) -> &mut Self {
141        let anagram_dist = anagram
142            .chars()
143            .map(|c| (c, 1))
144            .collect::<HashMap<char, usize>>();
145
146        tracing::debug!("Letter distribution: {:?}", anagram_dist);
147
148        self.retain(|word| {
149            let word_dist = word
150                .chars()
151                .map(|c| (c, 1))
152                .collect::<HashMap<char, usize>>();
153
154            word_dist == anagram_dist && word.len() == anagram.len() && word.as_str() != anagram
155        });
156
157        tracing::info!("There are {} anagrams of {}", self.len(), anagram);
158        self
159    }
160
161    fn filter_includes_specific_letters_in_volume(self, letters: &str) -> Self {
162        let mut letter_distribution = HashMap::new();
163        for letter in letters.chars() {
164            if letter != ' ' {
165                *letter_distribution.entry(letter).or_insert(0) += 1;
166            }
167        }
168
169        let mut output = Vec::new();
170
171        for word in self {
172            let mut test_dist = letter_distribution.clone();
173            let mut good_word = true;
174            for letter in word.chars() {
175                if test_dist.contains_key(&letter) && test_dist.get(&letter).unwrap() > &0 {
176                    *test_dist.get_mut(&letter).unwrap() -= 1;
177                } else {
178                    good_word = false;
179                    break;
180                }
181            }
182            if good_word {
183                output.push(word);
184            }
185        }
186
187        output
188    }
189}
190
191#[allow(dead_code)]
192fn no_repeated_letter(word: &str) -> bool {
193    let mut chars = word.chars();
194    let mut a = chars.next().unwrap();
195    for b in chars {
196        if a == b {
197            return false;
198        }
199        a = b
200    }
201
202    true
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    #[test]
209    fn test_no_repeated_letter() {
210        let words = vec!["ab", "aa", "all", "success", "fulfil", "greatness"];
211
212        let mut passed = vec![];
213        let mut failed = vec![];
214        for word in words {
215            if no_repeated_letter(word) {
216                passed.push(word);
217            } else {
218                failed.push(word);
219            }
220        }
221        assert_eq!(passed, vec!["ab", "fulfil"]);
222        assert_eq!(failed, vec!["aa", "all", "success", "greatness"]);
223    }
224
225    use super::WordFilters;
226
227    #[test]
228    fn test_filter_exclude_letters() {
229        let words = [
230            "ab",
231            "loser",
232            "all",
233            "success",
234            "fulfil",
235            "treat",
236            "greatness",
237        ];
238
239        let words = words.iter().map(|w| w.to_string()).collect::<Vec<String>>();
240
241        let filtered = words.filter_excludes_letters("cl");
242        assert_eq!(filtered, vec!["ab", "treat", "greatness"]);
243    }
244
245    #[test]
246    fn test_filter_include_all_letters() {
247        let words = [
248            "ab",
249            "loser",
250            "all",
251            "alternate",
252            "grown",
253            "success",
254            "fulfil",
255            "treat",
256            "greatness",
257        ];
258
259        let words = words.iter().map(|w| w.to_string()).collect::<Vec<String>>();
260
261        let filtered = words.filter_includes_all_letters("al");
262        assert_eq!(filtered, vec!["all", "alternate"]);
263    }
264
265    #[test]
266    fn test_filter_include_any_letters() {
267        let words = [
268            "ab",
269            "loser",
270            "all",
271            "grown",
272            "success",
273            "fulfil",
274            "treat",
275            "greatness",
276        ];
277
278        let words = words.iter().map(|w| w.to_string()).collect::<Vec<String>>();
279
280        let filtered = words.filter_includes_any_letters("al");
281        assert_eq!(
282            filtered,
283            vec!["ab", "loser", "all", "fulfil", "treat", "greatness",]
284        );
285    }
286
287    #[test]
288    fn test_filter_include_only_letters() {
289        let words = [
290            "ab",
291            "loser",
292            "all",
293            "success",
294            "fulfil",
295            "treat",
296            "greatness",
297        ];
298
299        let words = words.iter().map(|w| w.to_string()).collect::<Vec<String>>();
300
301        let mut filtered = words.clone();
302        filtered.filter_includes_only_letters("acubsel");
303        assert_eq!(filtered, vec!["ab", "all", "success"]);
304
305        let mut filtered = words;
306        filtered.filter_includes_only_letters("a cub sel");
307        assert_eq!(filtered, vec!["ab", "all", "success"]);
308    }
309
310    #[test]
311    fn test_filter_include_same_letters() {
312        let words = [
313            "ab",
314            "loser",
315            "all",
316            "simper",
317            "spirem",
318            "success",
319            "fulfil",
320            "treat",
321            "greatness",
322        ];
323
324        let words = words.iter().map(|w| w.to_string()).collect::<Vec<String>>();
325
326        let mut filtered = words.clone();
327        filtered.filter_includes_same_letters("primes");
328        assert_eq!(filtered, vec!["simper", "spirem"]);
329    }
330
331    #[test]
332    fn test_filter_letters_in_volume() {
333        let words = [
334            "ab",
335            "loser",
336            "all",
337            "simper",
338            "spirem",
339            "success",
340            "fulfil",
341            "treat",
342            "greatness",
343        ];
344
345        let words = words.iter().map(|w| w.to_string()).collect::<Vec<String>>();
346
347        let filtered = words.clone();
348        let filtered = filtered.filter_includes_specific_letters_in_volume("abloserimpucftgn");
349        assert_eq!(filtered, vec!["ab", "loser", "simper", "spirem"]);
350    }
351}