use std::collections::HashMap;
#[derive(Default)]
struct TrieNode {
children: HashMap<char, TrieNode>,
is_end: bool,
}
pub struct Trie {
root: TrieNode,
}
impl Trie {
#[must_use]
pub fn new() -> Self {
Self {
root: TrieNode::default(),
}
}
pub fn insert(&mut self, word: &str) {
let mut node = &mut self.root;
for ch in word.chars() {
node = node.children.entry(ch).or_default();
}
node.is_end = true;
}
#[must_use]
pub fn search(&self, word: &str) -> bool {
let mut node = &self.root;
for ch in word.chars() {
match node.children.get(&ch) {
Some(n) => node = n,
None => return false,
}
}
node.is_end
}
#[must_use]
pub fn starts_with(&self, prefix: &str) -> Vec<String> {
let mut node = &self.root;
for ch in prefix.chars() {
match node.children.get(&ch) {
Some(n) => node = n,
None => return vec![],
}
}
let mut results = Vec::new();
let mut buf = prefix.to_string();
Self::collect_words(node, &mut buf, &mut results);
results
}
fn collect_words(node: &TrieNode, buf: &mut String, results: &mut Vec<String>) {
if node.is_end {
results.push(buf.clone());
}
for (&ch, child) in &node.children {
buf.push(ch);
Self::collect_words(child, buf, results);
buf.pop();
}
}
}
impl Default for Trie {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn rabin_karp(text: &str, pattern: &str) -> Vec<usize> {
const BASE: u64 = 31;
const MOD: u64 = 1_000_000_007;
let pat: Vec<char> = pattern.chars().collect();
let m = pat.len();
let mut matches = Vec::new();
if m == 0 {
return matches;
}
let mut window: Vec<char> = Vec::with_capacity(m);
let mut chars = text.chars();
for _ in 0..m {
if let Some(c) = chars.next() {
window.push(c);
} else {
return matches; }
}
let mut pw = vec![1u64; m];
for i in 1..m {
pw[i] = pw[i - 1].wrapping_mul(BASE) % MOD;
}
let cv = |c: char| -> u64 { (c as u64).wrapping_add(1) };
let (mut ph, mut wh) = (0u64, 0u64);
for i in 0..m {
ph = (ph + cv(pat[i]) * pw[m - 1 - i]) % MOD;
wh = (wh + cv(window[i]) * pw[m - 1 - i]) % MOD;
}
let mut window_idx = 0;
let check_match = |window: &[char], start_idx: usize, pat: &[char]| -> bool {
for i in 0..m {
if window[(start_idx + i) % m] != pat[i] {
return false;
}
}
true
};
if wh == ph && check_match(&window, window_idx, &pat) {
matches.push(0);
}
for (i, next_char) in (1..).zip(chars) {
let old_char = window[window_idx];
wh = (wh + MOD - cv(old_char) * pw[m - 1] % MOD) % MOD;
wh = (wh * BASE) % MOD;
wh = (wh + cv(next_char)) % MOD;
window[window_idx] = next_char;
window_idx = (window_idx + 1) % m;
if wh == ph && check_match(&window, window_idx, &pat) {
matches.push(i);
}
}
matches
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trie_insert_search() {
let mut t = Trie::new();
t.insert("drone");
t.insert("droning");
assert!(t.search("drone"));
assert!(!t.search("dron"));
assert!(t.search("droning"));
}
#[test]
fn trie_starts_with() {
let mut t = Trie::new();
for w in &["alert", "alerting", "alarm", "base"] {
t.insert(w);
}
let mut r = t.starts_with("al");
r.sort();
assert_eq!(r, vec!["alarm", "alert", "alerting"]);
}
#[test]
fn rabin_karp_multiple() {
assert_eq!(rabin_karp("ababab", "ab"), vec![0, 2, 4]);
}
#[test]
fn rabin_karp_single() {
assert_eq!(rabin_karp("hello world", "world"), vec![6]);
}
#[test]
fn rabin_karp_no_match() {
assert!(rabin_karp("hello", "xyz").is_empty());
}
#[test]
fn empty_string_insert_and_search() {
let mut t = Trie::new();
t.insert("");
assert!(t.search(""));
assert!(!t.search("a"));
}
#[test]
fn search_not_inserted() {
let t = Trie::new();
assert!(!t.search("anything"));
}
#[test]
fn starts_with_empty_prefix() {
let mut t = Trie::new();
t.insert("alpha");
t.insert("beta");
let mut r = t.starts_with("");
r.sort();
assert_eq!(r, vec!["alpha", "beta"]);
}
#[test]
fn starts_with_no_matches() {
let mut t = Trie::new();
t.insert("hello");
assert!(t.starts_with("xyz").is_empty());
}
#[test]
fn unicode_support() {
let mut t = Trie::new();
t.insert("café");
t.insert("naïve");
assert!(t.search("café"));
assert!(!t.search("cafe"));
let r = t.starts_with("caf");
assert_eq!(r, vec!["café"]);
}
#[test]
fn rabin_karp_empty_pattern() {
assert!(rabin_karp("hello", "").is_empty());
}
#[test]
fn rabin_karp_unicode() {
assert_eq!(rabin_karp("aéaéa", "aé"), vec![0, 2]);
}
#[test]
fn rabin_karp_pattern_longer_than_text() {
assert!(rabin_karp("hi", "longer pattern").is_empty());
}
#[test]
fn rabin_karp_full_match() {
assert_eq!(rabin_karp("exact", "exact"), vec![0]);
}
#[test]
fn empty_trie_search_returns_false() {
let t = Trie::new();
assert!(!t.search("anything"));
assert!(!t.search(""));
}
#[test]
fn starts_with_no_prefix_match() {
let mut t = Trie::new();
t.insert("apple");
t.insert("banana");
assert!(t.starts_with("cherry").is_empty());
assert!(t.starts_with("app1").is_empty());
}
#[test]
fn insert_duplicate_is_idempotent() {
let mut t = Trie::new();
t.insert("hello");
t.insert("hello");
t.insert("hello");
assert!(t.search("hello"));
let results = t.starts_with("hello");
assert_eq!(results, vec!["hello"]);
}
#[test]
fn rabin_karp_no_matches() {
assert!(rabin_karp("abcdefgh", "xyz").is_empty());
assert!(rabin_karp("aaaa", "b").is_empty());
}
#[test]
fn rabin_karp_overlapping_patterns() {
assert_eq!(rabin_karp("aaaa", "aa"), vec![0, 1, 2]);
assert_eq!(rabin_karp("abababab", "abab"), vec![0, 2, 4]);
}
#[test]
fn rabin_karp_empty_text() {
assert!(rabin_karp("", "pattern").is_empty());
}
#[test]
fn rabin_karp_both_empty() {
assert!(rabin_karp("", "").is_empty());
}
#[test]
fn rabin_karp_single_char_pattern() {
assert_eq!(rabin_karp("abcabc", "a"), vec![0, 3]);
assert_eq!(rabin_karp("aaa", "a"), vec![0, 1, 2]);
}
#[test]
fn trie_prefix_is_not_word() {
let mut t = Trie::new();
t.insert("testing");
assert!(!t.search("test"));
assert!(!t.search("t"));
assert!(t.search("testing"));
}
#[test]
fn trie_starts_with_returns_exact_prefix_if_word() {
let mut t = Trie::new();
t.insert("test");
t.insert("testing");
t.insert("tested");
let mut r = t.starts_with("test");
r.sort();
assert_eq!(r, vec!["test", "tested", "testing"]);
}
#[test]
fn trie_default_is_empty() {
let t = Trie::default();
assert!(!t.search(""));
assert!(!t.search("a"));
}
#[test]
fn rabin_karp_repeated_pattern_in_text() {
let text = "xyzxyzxyz";
let matches = rabin_karp(text, "xyz");
assert_eq!(matches, vec![0, 3, 6]);
}
}