1use 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}