#![no_std]
extern crate alloc;
pub mod lookalikes;
use {
alloc::{boxed::Box, string::String, vec, vec::Vec},
core::mem::swap,
};
#[derive(Debug, Default)]
pub struct SearchTree {
nodes: Vec<(char, SearchTree)>,
end: Option<usize>,
}
impl<'key> FromIterator<(usize, &'key str)> for SearchTree {
fn from_iter<T: IntoIterator<Item = (usize, &'key str)>>(iter: T) -> Self {
let mut res = Self::default();
for (index, key) in iter {
res.push(key, index);
}
res
}
}
impl SearchTree {
pub fn get(&self, index: char) -> Option<&Self> {
if self.nodes.last().is_none_or(|(last, _)| index > *last) {
return None;
}
self.nodes
.binary_search_by_key(&index, |(ch, _)| *ch)
.ok()
.map(|i| &self.nodes[i].1)
}
pub fn push(&mut self, key: &str, index: usize) {
let mut iter = key.chars();
let Some(ch) = iter.next() else {
self.end = Some(index);
return;
};
let i = match self.nodes.binary_search_by_key(&ch, |(ch, _)| *ch) {
Ok(i) => i,
Err(i) => {
self.nodes.insert(i, (ch, Self::default()));
i
}
};
self.nodes[i].1.push(iter.as_str(), index);
}
fn for_each_base<E>(&self, f: &mut impl FnMut(usize) -> Result<(), E>) -> Result<(), E> {
self.end.map(&mut *f).transpose()?;
self.nodes
.iter()
.try_for_each(|(_, node)| node.for_each_base(f))
}
pub fn for_each<E>(&self, mut f: impl FnMut(usize) -> Result<(), E>) -> Result<(), E> {
self.for_each_base(&mut f)
}
}
pub struct Searcher<'tree> {
root: &'tree SearchTree,
input: String,
considered: Vec<&'tree SearchTree>,
new: Vec<&'tree SearchTree>,
lookalikes_buf: Vec<char>,
#[allow(clippy::type_complexity, reason = "it's not lol")]
lookalike_gen: Box<dyn FnMut(char, &mut Vec<char>)>,
}
impl Extend<char> for Searcher<'_> {
fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
for ch in iter {
self.push(ch);
}
}
}
impl<'tree> Searcher<'tree> {
pub fn new<I: Iterator<Item = char>>(
root: &'tree SearchTree,
mut iter_gen: impl 'static + FnMut(char) -> I,
) -> Self {
Self {
root,
input: String::new(),
considered: vec![root],
new: vec![],
lookalikes_buf: vec![],
lookalike_gen: Box::new(move |ch, dst| dst.extend(iter_gen(ch))),
}
}
pub const fn root(&self) -> &'tree SearchTree {
self.root
}
pub fn input(&self) -> &str {
self.input.as_str()
}
pub fn push(&mut self, ch: char) {
self.input.push(ch);
Self::compute_considerations(
ch,
&mut self.lookalikes_buf,
&mut self.lookalike_gen,
&mut self.considered,
&mut self.new,
);
}
fn compute_considerations(
ch: char,
lookalikes_buf: &mut Vec<char>,
lookalike_gen: &mut dyn FnMut(char, &mut Vec<char>),
considered: &mut Vec<&'tree SearchTree>,
new: &mut Vec<&'tree SearchTree>,
) {
lookalikes_buf.clear();
lookalikes_buf.push(ch);
lookalike_gen(ch, lookalikes_buf);
new.clear();
new.extend(
considered
.iter()
.flat_map(|n| lookalikes_buf.iter().filter_map(|&ch| n.get(ch))),
);
if !new.is_empty() {
swap(new, considered);
}
}
pub fn pop(&mut self) {
if self.input.pop().is_none() {
return;
}
self.considered.clear();
self.considered.push(self.root);
for ch in self.input.chars() {
Self::compute_considerations(
ch,
&mut self.lookalikes_buf,
&mut self.lookalike_gen,
&mut self.considered,
&mut self.new,
);
}
}
pub fn for_each_candidate<E>(
&self,
mut f: impl FnMut(usize) -> Result<(), E>,
) -> Result<(), E> {
self.considered
.iter()
.try_for_each(|n| n.for_each_base(&mut f))
}
}