1#![no_std]
7
8extern crate alloc;
9
10pub mod lookalikes;
11
12use {
13 alloc::{boxed::Box, string::String, vec, vec::Vec},
14 core::mem::swap,
15};
16
17#[derive(Debug, Default)]
19pub struct SearchTree {
20 nodes: Vec<(char, SearchTree)>,
21 end: Option<usize>,
22}
23
24impl<'key> FromIterator<(usize, &'key str)> for SearchTree {
25 fn from_iter<T: IntoIterator<Item = (usize, &'key str)>>(iter: T) -> Self {
26 let mut res = Self::default();
27 for (index, key) in iter {
28 res.push(key, index);
29 }
30 res
31 }
32}
33
34impl SearchTree {
35 pub fn get(&self, index: char) -> Option<&Self> {
37 if self.nodes.last().is_none_or(|(last, _)| index > *last) {
38 return None;
39 }
40
41 self.nodes
42 .binary_search_by_key(&index, |(ch, _)| *ch)
43 .ok()
44 .map(|i| &self.nodes[i].1)
45 }
46
47 pub fn push(&mut self, key: &str, index: usize) {
49 let mut iter = key.chars();
50 let Some(ch) = iter.next() else {
51 self.end = Some(index);
52 return;
53 };
54
55 let i = match self.nodes.binary_search_by_key(&ch, |(ch, _)| *ch) {
56 Ok(i) => i,
57 Err(i) => {
58 self.nodes.insert(i, (ch, Self::default()));
59 i
60 }
61 };
62
63 self.nodes[i].1.push(iter.as_str(), index);
64 }
65
66 fn for_each_base<E>(&self, f: &mut impl FnMut(usize) -> Result<(), E>) -> Result<(), E> {
67 self.end.map(&mut *f).transpose()?;
68 self.nodes
69 .iter()
70 .try_for_each(|(_, node)| node.for_each_base(f))
71 }
72
73 pub fn for_each<E>(&self, mut f: impl FnMut(usize) -> Result<(), E>) -> Result<(), E> {
78 self.for_each_base(&mut f)
79 }
80}
81
82pub struct Searcher<'tree> {
84 root: &'tree SearchTree,
85 input: String,
86 considered: Vec<&'tree SearchTree>,
88 new: Vec<&'tree SearchTree>,
90 lookalikes_buf: Vec<char>,
92 #[allow(clippy::type_complexity, reason = "it's not lol")]
93 lookalike_gen: Box<dyn FnMut(char, &mut Vec<char>)>,
94}
95
96impl Extend<char> for Searcher<'_> {
97 fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
98 for ch in iter {
99 self.push(ch);
100 }
101 }
102}
103
104impl<'tree> Searcher<'tree> {
105 pub fn new<I: Iterator<Item = char>>(
121 root: &'tree SearchTree,
122 mut iter_gen: impl 'static + FnMut(char) -> I,
123 ) -> Self {
124 Self {
125 root,
126 input: String::new(),
127 considered: vec![root],
128 new: vec![],
129 lookalikes_buf: vec![],
130 lookalike_gen: Box::new(move |ch, dst| dst.extend(iter_gen(ch))),
131 }
132 }
133
134 pub const fn root(&self) -> &'tree SearchTree {
135 self.root
136 }
137
138 pub fn input(&self) -> &str {
139 self.input.as_str()
140 }
141
142 pub fn push(&mut self, ch: char) {
144 self.input.push(ch);
145 Self::compute_considerations(
146 ch,
147 &mut self.lookalikes_buf,
148 &mut self.lookalike_gen,
149 &mut self.considered,
150 &mut self.new,
151 );
152 }
153
154 fn compute_considerations(
156 ch: char,
157 lookalikes_buf: &mut Vec<char>,
158 lookalike_gen: &mut dyn FnMut(char, &mut Vec<char>),
159 considered: &mut Vec<&'tree SearchTree>,
160 new: &mut Vec<&'tree SearchTree>,
161 ) {
162 lookalikes_buf.clear();
163 lookalikes_buf.push(ch);
164 lookalike_gen(ch, lookalikes_buf);
165
166 new.clear();
167 new.extend(
168 considered
169 .iter()
170 .flat_map(|n| lookalikes_buf.iter().filter_map(|&ch| n.get(ch))),
171 );
172
173 if !new.is_empty() {
174 swap(new, considered);
175 }
176 }
177
178 pub fn pop(&mut self) {
180 if self.input.pop().is_none() {
181 return;
182 }
183 self.considered.clear();
184 self.considered.push(self.root);
185 for ch in self.input.chars() {
186 Self::compute_considerations(
187 ch,
188 &mut self.lookalikes_buf,
189 &mut self.lookalike_gen,
190 &mut self.considered,
191 &mut self.new,
192 );
193 }
194 }
195
196 pub fn for_each_candidate<E>(
201 &self,
202 mut f: impl FnMut(usize) -> Result<(), E>,
203 ) -> Result<(), E> {
204 self.considered
205 .iter()
206 .try_for_each(|n| n.for_each_base(&mut f))
207 }
208}