better_profanity/
words.rs1use rustc_hash::FxHashMap;
2
3#[derive(Clone, Debug)]
13pub struct WordSet {
14 nodes: Vec<Node>,
15}
16
17#[derive(Clone, Debug, Default)]
18struct Node {
19 terminal: bool,
20 edges: Box<[(char, u32)]>,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub struct SetId(u32);
27
28#[derive(Debug)]
30pub struct WordSetBuilder {
31 sets: Vec<Box<[char]>>,
32 set_ids: FxHashMap<Box<[char]>, SetId>,
33 children: FxHashMap<(u32, SetId), u32>,
35 terminal: Vec<bool>,
36 scratch: Vec<char>,
37 ids: Vec<SetId>,
38}
39
40impl Default for WordSetBuilder {
41 fn default() -> Self {
42 WordSetBuilder {
43 sets: Vec::new(),
44 set_ids: FxHashMap::default(),
45 children: FxHashMap::default(),
46 terminal: vec![false],
47 scratch: Vec::new(),
48 ids: Vec::new(),
49 }
50 }
51}
52
53impl WordSetBuilder {
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn intern(&mut self, chars: impl IntoIterator<Item = char>) -> SetId {
60 self.scratch.clear();
61 self.scratch.extend(chars);
62 self.scratch.sort_unstable();
63 self.scratch.dedup();
64 if let Some(&id) = self.set_ids.get(self.scratch.as_slice()) {
65 return id;
66 }
67 let id = SetId(self.sets.len() as u32);
68 let set: Box<[char]> = self.scratch.as_slice().into();
69 self.sets.push(set.clone());
70 self.set_ids.insert(set, id);
71 id
72 }
73
74 pub fn is_empty_set(&self, id: SetId) -> bool {
76 self.sets[id.0 as usize].is_empty()
77 }
78
79 pub fn insert_sets(&mut self, positions: &[SetId]) {
81 let mut node = 0u32;
82 for &set in positions {
83 let next = self.terminal.len() as u32;
84 node = *self.children.entry((node, set)).or_insert(next);
85 if node == next {
86 self.terminal.push(false);
87 }
88 }
89 self.terminal[node as usize] = true;
90 }
91
92 pub fn insert<P, C>(&mut self, positions: P)
94 where
95 P: IntoIterator<Item = C>,
96 C: IntoIterator<Item = char>,
97 {
98 let mut ids = std::mem::take(&mut self.ids);
99 ids.clear();
100 ids.extend(positions.into_iter().map(|p| self.intern(p)));
101 self.insert_sets(&ids);
102 self.ids = ids;
103 }
104
105 pub fn build(self) -> WordSet {
106 let mut edges: Vec<Vec<(char, u32)>> = vec![Vec::new(); self.terminal.len()];
107 for (&(node, set), &child) in &self.children {
108 let set = &self.sets[set.0 as usize];
109 edges[node as usize].extend(set.iter().map(|&c| (c, child)));
110 }
111 let nodes = edges
112 .into_iter()
113 .zip(self.terminal)
114 .map(|(mut edges, terminal)| {
115 edges.sort_unstable();
116 Node {
117 terminal,
118 edges: edges.into_boxed_slice(),
119 }
120 })
121 .collect();
122 WordSet { nodes }
123 }
124}
125
126#[derive(Clone, Debug, Default)]
128pub struct Walk {
129 states: Vec<u32>,
130 next: Vec<u32>,
131}
132
133impl Walk {
134 pub fn is_dead(&self) -> bool {
136 self.states.is_empty()
137 }
138
139 pub(crate) fn copy_from(&mut self, other: &Walk) {
140 self.states.clear();
141 self.states.extend_from_slice(&other.states);
142 }
143}
144
145impl WordSet {
146 pub fn builder() -> WordSetBuilder {
147 WordSetBuilder::new()
148 }
149
150 pub fn is_empty(&self) -> bool {
152 !self.nodes[0].terminal && self.nodes[0].edges.is_empty()
153 }
154
155 pub fn reset(&self, walk: &mut Walk) {
157 walk.states.clear();
158 walk.states.push(0);
159 }
160
161 pub fn step(&self, walk: &mut Walk, c: char) {
162 walk.next.clear();
163 for &state in &walk.states {
164 let edges = &self.nodes[state as usize].edges;
165 let start = edges.partition_point(|&(e, _)| e < c);
166 for &(e, child) in &edges[start..] {
167 if e != c {
168 break;
169 }
170 walk.next.push(child);
171 }
172 }
173 std::mem::swap(&mut walk.states, &mut walk.next);
174 }
175
176 pub fn accepts(&self, walk: &Walk) -> bool {
178 walk.states.iter().any(|&s| self.nodes[s as usize].terminal)
179 }
180
181 pub fn contains(&self, s: impl IntoIterator<Item = char>) -> bool {
183 let mut walk = Walk::default();
184 self.reset(&mut walk);
185 for c in s {
186 self.step(&mut walk, c);
187 if walk.is_dead() {
188 return false;
189 }
190 }
191 self.accepts(&walk)
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn set(words: &[&[&str]]) -> WordSet {
200 let mut b = WordSet::builder();
201 for w in words {
202 b.insert(w.iter().map(|p| p.chars()));
203 }
204 b.build()
205 }
206
207 #[test]
208 fn matches_each_position() {
209 let s = set(&[&["a@*", "s$", "s$"], &["o*0", "k"], &["ab"]]);
210 assert!(s.contains("ass".chars()));
211 assert!(s.contains("*$s".chars()));
212 assert!(s.contains("*k".chars()));
213 assert!(s.contains("b".chars()));
214 assert!(!s.contains("as".chars()));
215 assert!(!s.contains("asss".chars()));
216 assert!(!s.contains("".chars()));
217 assert!(!s.is_empty());
218 assert!(set(&[]).is_empty());
219 assert!(set(&[&[]]).contains("".chars()));
220 }
221}