Skip to main content

tree_table/utils/
utils_fns.rs

1use crate::{GetResultExt, NaturalSegment, OrderedF64};
2use alloc::rc::Rc;
3use alloc::string::String;
4use alloc::vec;
5use alloc::vec::Vec;
6use core::sync::atomic::{AtomicU32, Ordering};
7use hashbrown::{HashMap, HashSet};
8
9static WIDGET_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
10
11pub(crate) fn generate_unique_id() -> u32 {
12    WIDGET_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
13}
14
15const ALPHABET: [char; 62] = [
16    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
17    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
18    'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
19    '5', '6', '7', '8', '9',
20];
21
22pub(crate) fn remove_duplicates_outside_section(
23    items: Vec<Rc<str>>,
24    section_start: usize,
25    section_size: usize,
26) -> Vec<Rc<str>> {
27    if section_start == 0 && section_size >= items.len() {
28        return items;
29    }
30    let section_end = section_start + section_size;
31    let section_set: HashSet<Rc<str>> = items[section_start..section_end].iter().cloned().collect();
32    items
33        .into_iter()
34        .enumerate()
35        .filter(|(i, s)| {
36            *i >= section_start && *i < section_end || !section_set.contains(s.as_ref())
37        })
38        .map(|(_, s)| s)
39        .collect()
40}
41
42/// Faster way to find the position (index) of a usize in a
43/// sorted Sequence of usizes
44#[inline]
45pub(crate) fn bin_index_usize(sorted_seq: &[usize], find: usize) -> Option<usize> {
46    sorted_seq.binary_search(&find).ok()
47}
48
49#[inline]
50pub(crate) fn push_n(k: usize, sorted_seq: &[usize]) -> usize {
51    if sorted_seq.is_empty() || k < sorted_seq[0] {
52        k
53    } else {
54        let mut lo: usize = 0;
55        let mut hi = sorted_seq.len();
56        while lo < hi {
57            let mid = lo + (hi - lo) / 2;
58            if sorted_seq[mid] < k + mid + 1 {
59                lo = mid + 1;
60            } else {
61                hi = mid;
62            }
63        }
64        k + lo
65    }
66}
67
68#[inline]
69pub(crate) fn pull_n(k: usize, sorted_seq: &[usize]) -> usize {
70    k - sorted_seq.binary_search(&k).unwrap_or_else(|e| e)
71}
72
73#[inline]
74pub(crate) fn push_n_add<T>(k: usize, sorted_seq: &[(usize, T)]) -> usize {
75    if sorted_seq.is_empty() || k < sorted_seq[0].0 {
76        k
77    } else {
78        let mut lo: usize = 0;
79        let mut hi: usize = sorted_seq.len();
80        while lo < hi {
81            let mid = lo + (hi - lo) / 2;
82            if sorted_seq[mid].0 < k + mid + 1 {
83                lo = mid + 1;
84            } else {
85                hi = mid;
86            }
87        }
88        k + lo
89    }
90}
91
92pub(crate) fn offset_move_indices(
93    move_to: usize,
94    to_move: &[usize],
95    new_to_old: &mut HashMap<usize, usize>,
96) -> Vec<(usize, usize)> {
97    // Count elements in to_move less than move_to
98    let offset = to_move.iter().filter(|i| *i < &move_to).count();
99
100    // Adjust move_to, preventing underflow
101    let correct_move_to = move_to.saturating_sub(offset);
102
103    // Initialize hashmaps
104    let mut old_to_new = Vec::new();
105    new_to_old.clear();
106
107    // Map old indices to new and vice versa
108    for (i, &elem) in to_move.iter().enumerate() {
109        let value = correct_move_to + i;
110        old_to_new.push((elem, value));
111        new_to_old.insert(value, elem);
112    }
113
114    old_to_new
115}
116
117/// Creates two Vecs:
118/// (full_old_to_new, full_new_to_old)
119/// full_old_to_new
120///     - A Vec of new indices in their old Vec positions.
121/// full_new_to_old
122///     - A Vec of old indices in their new Vec positions.
123pub(crate) fn get_full_move_map(
124    len: usize,
125    old: &HashSet<usize>,
126    new_to_old: &HashMap<usize, usize>, // still sparse — unchanged
127) -> (Vec<usize>, Vec<usize>) {
128    // (full_old_to_new: old→new, full_new_to_old: new→old)
129    let mut full_old_to_new = vec![0usize; len]; // index = old pos, value = new pos
130    let mut full_new_to_old = vec![0usize; len]; // index = new pos, value = old pos
131
132    let mut remaining_old = (0..len).filter(|&i| !old.contains(&i));
133
134    for new_i in 0..len {
135        let old_i = if let Some(&oi) = new_to_old.get(&new_i) {
136            oi
137        } else {
138            remaining_old.next().unwrap_or_else(|| new_i) // fallback, should never hit
139        };
140
141        full_new_to_old[new_i] = old_i;
142        full_old_to_new[old_i] = new_i;
143    }
144
145    (full_old_to_new, full_new_to_old)
146}
147
148/// Converts a usize to a compact String using base-62 bijective encoding (A-Z, a-z, 0-9).
149/// This is more efficient than base-26 as it uses 62 symbols, resulting in shorter strings.
150/// It's repeatable: same input yields same output.
151/// Handles usize::MAX as a special case to avoid overflow.
152#[inline(always)]
153pub fn n_to_base62(n: usize) -> String {
154    let base: usize = 62;
155    let mut s = String::with_capacity(11);
156    let mut m = n + 1;
157    while m > 0 {
158        let rem = (m - 1) % base;
159        s.push(ALPHABET[rem as usize]);
160        m = (m - 1) / base;
161    }
162    s.chars().rev().collect::<String>()
163}
164
165/// Performs a binary search on a sorted vec of IIDs to find the leftmost insertion point
166/// where the row >= target_row. Assumes the vec is sorted by row numbers.
167/// Returns the insertion index or an error if any accessed IID lacks a row mapping.
168pub fn binary_search_ins_pos(
169    v: &[Rc<str>],
170    iid_to_row: &HashMap<Rc<str>, usize>,
171    target_row: usize,
172) -> Result<usize, String> {
173    let mut lo = 0;
174    let mut hi = v.len();
175    while lo < hi {
176        let mid = lo + (hi - lo) / 2;
177        let row = *iid_to_row.get_res(&v[mid])?;
178        if row < target_row {
179            lo = mid + 1;
180        } else {
181            hi = mid;
182        }
183    }
184    Ok(lo)
185}
186
187/// Performs a binary search on a sorted vec of IIDs to find the exact position
188/// where the row == target_row. Assumes the vec is sorted by row numbers.
189/// Returns Some(pos) if found, None if not present, or an error if any accessed IID lacks a row mapping.
190pub fn binary_search_exact_pos(
191    v: &[Rc<str>],
192    iid_to_row: &HashMap<Rc<str>, usize>,
193    target_row: usize,
194) -> Result<Option<usize>, String> {
195    let mut lo = 0;
196    let mut hi = v.len();
197    while lo < hi {
198        let mid = lo + (hi - lo) / 2;
199        let row = *iid_to_row.get_res(&v[mid])?;
200        if row < target_row {
201            lo = mid + 1;
202        } else if row > target_row {
203            hi = mid;
204        } else {
205            return Ok(Some(mid));
206        }
207    }
208    Ok(None)
209}
210
211#[inline(always)]
212fn is_decimal_digit(c: char) -> bool {
213    c.to_digit(10).is_some()
214}
215
216#[inline(always)]
217pub fn natural_text_segments(s: &str) -> Vec<NaturalSegment> {
218    let mut segments = Vec::with_capacity(8);
219    let mut chars = s.chars().peekable();
220    let mut text_buf = String::with_capacity(32);
221
222    while let Some(c) = chars.next() {
223        // 1. Signed number: "-42", "+7", "−3"
224        if matches!(c, '-' | '+' | '\u{2212}')
225            && chars.peek().map_or(false, |&n| is_decimal_digit(n))
226        {
227            if !text_buf.is_empty() {
228                segments.push(NaturalSegment::Text(text_buf.to_lowercase()));
229                text_buf.clear();
230            }
231            let negative = c == '-' || c == '\u{2212}';
232            let mut value: i64 = 0;
233
234            // Consume first digit (guaranteed by the peek)
235            if let Some(d) = chars.next().and_then(|ch| ch.to_digit(10)) {
236                value = d as i64;
237            }
238            // Remaining digits
239            while let Some(&next) = chars.peek() {
240                if let Some(nd) = next.to_digit(10) {
241                    value = value.saturating_mul(10).saturating_add(nd as i64);
242                    chars.next();
243                } else {
244                    break;
245                }
246            }
247            let num = if negative { -value } else { value };
248            segments.push(NaturalSegment::Number(OrderedF64(num as f64)));
249            continue;
250        }
251
252        // 2. Unsigned digit run
253        if let Some(d) = c.to_digit(10) {
254            if !text_buf.is_empty() {
255                segments.push(NaturalSegment::Text(text_buf.to_lowercase()));
256                text_buf.clear();
257            }
258            let mut value = d as i64;
259            while let Some(&next) = chars.peek() {
260                if let Some(nd) = next.to_digit(10) {
261                    value = value.saturating_mul(10).saturating_add(nd as i64);
262                    chars.next();
263                } else {
264                    break;
265                }
266            }
267            segments.push(NaturalSegment::Number(OrderedF64(value as f64)));
268            continue;
269        }
270
271        // 3. Regular text
272        text_buf.push(c);
273    }
274
275    if !text_buf.is_empty() {
276        segments.push(NaturalSegment::Text(text_buf.to_lowercase()));
277    }
278
279    segments
280}
281
282#[inline(always)]
283pub fn natural_text_segments_cs(s: &str) -> Vec<NaturalSegment> {
284    let mut segments = Vec::with_capacity(8);
285    let mut chars = s.chars().peekable();
286    let mut text_buf = String::with_capacity(32);
287
288    while let Some(c) = chars.next() {
289        // 1. Signed number: "-42", "+7", "−3"
290        if matches!(c, '-' | '+' | '\u{2212}')
291            && chars.peek().map_or(false, |&n| is_decimal_digit(n))
292        {
293            if !text_buf.is_empty() {
294                segments.push(NaturalSegment::Text(text_buf.clone()));
295                text_buf.clear();
296            }
297            let negative = c == '-' || c == '\u{2212}';
298            let mut value: i64 = 0;
299
300            // Consume first digit (guaranteed by the peek)
301            if let Some(d) = chars.next().and_then(|ch| ch.to_digit(10)) {
302                value = d as i64;
303            }
304            // Remaining digits
305            while let Some(&next) = chars.peek() {
306                if let Some(nd) = next.to_digit(10) {
307                    value = value.saturating_mul(10).saturating_add(nd as i64);
308                    chars.next();
309                } else {
310                    break;
311                }
312            }
313            let num = if negative { -value } else { value };
314            segments.push(NaturalSegment::Number(OrderedF64(num as f64)));
315            continue;
316        }
317
318        // 2. Unsigned digit run
319        if let Some(d) = c.to_digit(10) {
320            if !text_buf.is_empty() {
321                segments.push(NaturalSegment::Text(text_buf.clone()));
322                text_buf.clear();
323            }
324            let mut value = d as i64;
325            while let Some(&next) = chars.peek() {
326                if let Some(nd) = next.to_digit(10) {
327                    value = value.saturating_mul(10).saturating_add(nd as i64);
328                    chars.next();
329                } else {
330                    break;
331                }
332            }
333            segments.push(NaturalSegment::Number(OrderedF64(value as f64)));
334            continue;
335        }
336
337        // 3. Regular text
338        text_buf.push(c);
339    }
340
341    if !text_buf.is_empty() {
342        segments.push(NaturalSegment::Text(text_buf));
343    }
344
345    segments
346}