tensor4all_tcicore/indexset.rs
1//! Index set for managing ordered collections with bidirectional lookup.
2//!
3//! [`IndexSet`] maintains insertion order while providing O(1) lookup in both
4//! directions: from positional index to value and from value to positional
5//! index. Duplicate insertions are silently ignored.
6//!
7//! This is used throughout the TCI infrastructure for managing pivot indices
8//! in matrix cross interpolation algorithms.
9
10use std::collections::HashMap;
11use std::hash::Hash;
12
13/// A bidirectional index set for efficient lookup
14///
15/// Provides O(1) lookup from integer index to value and from value to integer index.
16///
17/// # Examples
18///
19/// ```
20/// use tensor4all_tcicore::IndexSet;
21///
22/// let mut set: IndexSet<String> = IndexSet::new();
23/// set.push("alpha".to_string());
24/// set.push("beta".to_string());
25/// set.push("alpha".to_string()); // duplicate, ignored
26///
27/// assert_eq!(set.len(), 2);
28/// assert_eq!(set.get(0), Some(&"alpha".to_string()));
29/// assert_eq!(set.pos(&"beta".to_string()), Some(1));
30/// assert!(set.contains(&"alpha".to_string()));
31/// assert!(!set.contains(&"gamma".to_string()));
32/// ```
33#[derive(Debug, Clone)]
34pub struct IndexSet<T: Clone + Eq + Hash> {
35 /// Map from value to integer index
36 to_int: HashMap<T, usize>,
37 /// Map from integer index to value
38 from_int: Vec<T>,
39}
40
41impl<T: Clone + Eq + Hash> Default for IndexSet<T> {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl<T: Clone + Eq + Hash> IndexSet<T> {
48 /// Create an empty index set.
49 ///
50 /// # Examples
51 ///
52 /// ```
53 /// use tensor4all_tcicore::IndexSet;
54 ///
55 /// let set: IndexSet<usize> = IndexSet::new();
56 /// assert!(set.is_empty());
57 /// assert_eq!(set.len(), 0);
58 /// ```
59 pub fn new() -> Self {
60 Self {
61 to_int: HashMap::new(),
62 from_int: Vec::new(),
63 }
64 }
65
66 /// Create an index set from a vector
67 ///
68 /// Duplicate values are removed, keeping the first occurrence.
69 ///
70 /// # Examples
71 ///
72 /// ```
73 /// use tensor4all_tcicore::IndexSet;
74 ///
75 /// let set = IndexSet::from_vec(vec![10usize, 20, 10, 30]);
76 /// assert_eq!(set.len(), 3);
77 /// assert_eq!(set[0], 10);
78 /// assert_eq!(set[1], 20);
79 /// assert_eq!(set[2], 30);
80 /// ```
81 pub fn from_vec(values: Vec<T>) -> Self {
82 let mut to_int = HashMap::new();
83 let mut from_int = Vec::new();
84 for value in values {
85 if !to_int.contains_key(&value) {
86 let idx = from_int.len();
87 to_int.insert(value.clone(), idx);
88 from_int.push(value);
89 }
90 }
91 Self { to_int, from_int }
92 }
93
94 /// Get the value at a positional index, or `None` if out of range.
95 ///
96 /// # Examples
97 ///
98 /// ```
99 /// use tensor4all_tcicore::IndexSet;
100 ///
101 /// let set = IndexSet::from_vec(vec![10, 20, 30]);
102 /// assert_eq!(set.get(0), Some(&10));
103 /// assert_eq!(set.get(2), Some(&30));
104 /// assert_eq!(set.get(3), None);
105 /// ```
106 pub fn get(&self, i: usize) -> Option<&T> {
107 self.from_int.get(i)
108 }
109
110 /// Get the positional index of a value, or `None` if not present.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use tensor4all_tcicore::IndexSet;
116 ///
117 /// let set = IndexSet::from_vec(vec![10, 20, 30]);
118 /// assert_eq!(set.pos(&20), Some(1));
119 /// assert_eq!(set.pos(&99), None);
120 /// ```
121 pub fn pos(&self, value: &T) -> Option<usize> {
122 self.to_int.get(value).copied()
123 }
124
125 /// Get positional indices for a slice of values.
126 ///
127 /// Returns `None` if any value is not present in the set.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use tensor4all_tcicore::IndexSet;
133 ///
134 /// let set = IndexSet::from_vec(vec![10, 20, 30]);
135 /// assert_eq!(set.positions(&[30, 10]), Some(vec![2, 0]));
136 /// assert_eq!(set.positions(&[10, 99]), None);
137 /// ```
138 pub fn positions(&self, values: &[T]) -> Option<Vec<usize>> {
139 values.iter().map(|v| self.pos(v)).collect()
140 }
141
142 /// Push a new value to the set.
143 ///
144 /// If the value already exists in the set, this is a no-op.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// use tensor4all_tcicore::IndexSet;
150 ///
151 /// let mut set = IndexSet::new();
152 /// set.push(10);
153 /// set.push(20);
154 /// set.push(10); // duplicate, ignored
155 /// assert_eq!(set.len(), 2);
156 /// assert_eq!(set[0], 10);
157 /// assert_eq!(set[1], 20);
158 /// ```
159 pub fn push(&mut self, value: T) {
160 if self.to_int.contains_key(&value) {
161 return;
162 }
163 let idx = self.from_int.len();
164 self.from_int.push(value.clone());
165 self.to_int.insert(value, idx);
166 }
167
168 /// Check if the set contains a value.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// use tensor4all_tcicore::IndexSet;
174 ///
175 /// let set = IndexSet::from_vec(vec![10, 20]);
176 /// assert!(set.contains(&10));
177 /// assert!(!set.contains(&30));
178 /// ```
179 pub fn contains(&self, value: &T) -> bool {
180 self.to_int.contains_key(value)
181 }
182
183 /// Number of elements in the set
184 pub fn len(&self) -> usize {
185 self.from_int.len()
186 }
187
188 /// Check if the set is empty
189 pub fn is_empty(&self) -> bool {
190 self.from_int.is_empty()
191 }
192
193 /// Iterate over values in insertion order.
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// use tensor4all_tcicore::IndexSet;
199 ///
200 /// let set = IndexSet::from_vec(vec![10, 20, 30]);
201 /// let collected: Vec<_> = set.iter().copied().collect();
202 /// assert_eq!(collected, vec![10, 20, 30]);
203 /// ```
204 pub fn iter(&self) -> impl Iterator<Item = &T> {
205 self.from_int.iter()
206 }
207
208 /// Get all values as a slice in insertion order.
209 ///
210 /// # Examples
211 ///
212 /// ```
213 /// use tensor4all_tcicore::IndexSet;
214 ///
215 /// let set = IndexSet::from_vec(vec![10, 20, 30]);
216 /// assert_eq!(set.values(), &[10, 20, 30]);
217 /// ```
218 pub fn values(&self) -> &[T] {
219 &self.from_int
220 }
221}
222
223impl<T: Clone + Eq + Hash> std::ops::Index<usize> for IndexSet<T> {
224 type Output = T;
225
226 fn index(&self, i: usize) -> &Self::Output {
227 &self.from_int[i]
228 }
229}
230
231impl<T: Clone + Eq + Hash> IntoIterator for IndexSet<T> {
232 type Item = T;
233 type IntoIter = std::vec::IntoIter<T>;
234
235 fn into_iter(self) -> Self::IntoIter {
236 self.from_int.into_iter()
237 }
238}
239
240impl<'a, T: Clone + Eq + Hash> IntoIterator for &'a IndexSet<T> {
241 type Item = &'a T;
242 type IntoIter = std::slice::Iter<'a, T>;
243
244 fn into_iter(self) -> Self::IntoIter {
245 self.from_int.iter()
246 }
247}
248
249/// A multi-index: a vector of site-local indices.
250pub type MultiIndex = Vec<usize>;
251
252/// A single site-local index.
253pub type LocalIndex = usize;
254
255#[cfg(test)]
256mod tests;