Skip to main content

oxihuman_core/
string_set.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! An ordered set of strings with fast lookup and sorted iteration.
6
7use std::collections::BTreeSet;
8
9/// An ordered set of owned strings.
10#[allow(dead_code)]
11pub struct StringSet {
12    inner: BTreeSet<String>,
13    add_count: u64,
14    remove_count: u64,
15}
16
17#[allow(dead_code)]
18impl StringSet {
19    pub fn new() -> Self {
20        Self {
21            inner: BTreeSet::new(),
22            add_count: 0,
23            remove_count: 0,
24        }
25    }
26
27    pub fn insert(&mut self, s: &str) -> bool {
28        let inserted = self.inner.insert(s.to_string());
29        if inserted {
30            self.add_count += 1;
31        }
32        inserted
33    }
34
35    pub fn remove(&mut self, s: &str) -> bool {
36        let removed = self.inner.remove(s);
37        if removed {
38            self.remove_count += 1;
39        }
40        removed
41    }
42
43    pub fn contains(&self, s: &str) -> bool {
44        self.inner.contains(s)
45    }
46
47    pub fn len(&self) -> usize {
48        self.inner.len()
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.inner.is_empty()
53    }
54
55    pub fn to_vec(&self) -> Vec<String> {
56        self.inner.iter().cloned().collect()
57    }
58
59    pub fn clear(&mut self) {
60        self.inner.clear();
61    }
62
63    /// Returns elements present in both sets.
64    pub fn intersection(&self, other: &StringSet) -> StringSet {
65        let mut result = StringSet::new();
66        for s in &self.inner {
67            if other.contains(s) {
68                result.insert(s);
69            }
70        }
71        result
72    }
73
74    /// Returns elements present in either set.
75    pub fn union(&self, other: &StringSet) -> StringSet {
76        let mut result = StringSet::new();
77        for s in &self.inner {
78            result.insert(s);
79        }
80        for s in &other.inner {
81            result.insert(s);
82        }
83        result
84    }
85
86    /// Returns elements in self but not in other.
87    pub fn difference(&self, other: &StringSet) -> StringSet {
88        let mut result = StringSet::new();
89        for s in &self.inner {
90            if !other.contains(s) {
91                result.insert(s);
92            }
93        }
94        result
95    }
96
97    pub fn add_count(&self) -> u64 {
98        self.add_count
99    }
100    pub fn remove_count(&self) -> u64 {
101        self.remove_count
102    }
103
104    pub fn starts_with_prefix(&self, prefix: &str) -> Vec<String> {
105        self.inner
106            .iter()
107            .filter(|s| s.starts_with(prefix))
108            .cloned()
109            .collect()
110    }
111}
112
113impl Default for StringSet {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119pub fn new_string_set() -> StringSet {
120    StringSet::new()
121}
122
123pub fn ss_insert(set: &mut StringSet, s: &str) -> bool {
124    set.insert(s)
125}
126
127pub fn ss_contains(set: &StringSet, s: &str) -> bool {
128    set.contains(s)
129}
130
131pub fn ss_remove(set: &mut StringSet, s: &str) -> bool {
132    set.remove(s)
133}
134
135pub fn ss_len(set: &StringSet) -> usize {
136    set.len()
137}
138
139pub fn ss_to_vec(set: &StringSet) -> Vec<String> {
140    set.to_vec()
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn new_is_empty() {
149        let s = new_string_set();
150        assert!(s.is_empty());
151    }
152
153    #[test]
154    fn insert_and_contains() {
155        let mut s = new_string_set();
156        ss_insert(&mut s, "hello");
157        assert!(ss_contains(&s, "hello"));
158        assert!(!ss_contains(&s, "world"));
159    }
160
161    #[test]
162    fn insert_duplicate_returns_false() {
163        let mut s = new_string_set();
164        assert!(ss_insert(&mut s, "x"));
165        assert!(!ss_insert(&mut s, "x"));
166        assert_eq!(ss_len(&s), 1);
167    }
168
169    #[test]
170    fn remove_present() {
171        let mut s = new_string_set();
172        ss_insert(&mut s, "y");
173        assert!(ss_remove(&mut s, "y"));
174        assert!(!ss_contains(&s, "y"));
175    }
176
177    #[test]
178    fn sorted_iteration() {
179        let mut s = new_string_set();
180        ss_insert(&mut s, "c");
181        ss_insert(&mut s, "a");
182        ss_insert(&mut s, "b");
183        assert_eq!(
184            ss_to_vec(&s),
185            vec!["a".to_string(), "b".to_string(), "c".to_string()]
186        );
187    }
188
189    #[test]
190    fn intersection() {
191        let mut a = new_string_set();
192        let mut b = new_string_set();
193        ss_insert(&mut a, "x");
194        ss_insert(&mut a, "y");
195        ss_insert(&mut b, "y");
196        ss_insert(&mut b, "z");
197        let inter = a.intersection(&b);
198        assert_eq!(inter.to_vec(), vec!["y".to_string()]);
199    }
200
201    #[test]
202    fn union_combines() {
203        let mut a = new_string_set();
204        let mut b = new_string_set();
205        ss_insert(&mut a, "x");
206        ss_insert(&mut b, "y");
207        assert_eq!(a.union(&b).len(), 2);
208    }
209
210    #[test]
211    fn difference() {
212        let mut a = new_string_set();
213        let mut b = new_string_set();
214        ss_insert(&mut a, "x");
215        ss_insert(&mut a, "y");
216        ss_insert(&mut b, "y");
217        let diff = a.difference(&b);
218        assert_eq!(diff.to_vec(), vec!["x".to_string()]);
219    }
220
221    #[test]
222    fn starts_with_prefix() {
223        let mut s = new_string_set();
224        ss_insert(&mut s, "asset_mesh");
225        ss_insert(&mut s, "asset_tex");
226        ss_insert(&mut s, "config");
227        let matches = s.starts_with_prefix("asset_");
228        assert_eq!(matches.len(), 2);
229    }
230
231    #[test]
232    fn clear_empties_set() {
233        let mut s = new_string_set();
234        ss_insert(&mut s, "a");
235        s.clear();
236        assert!(s.is_empty());
237    }
238}