Skip to main content

radiate_core/domain/math/
indexes.rs

1use crate::{RdRand, random_provider};
2
3pub enum SubsetMode<'a> {
4    StratifiedCorrect,
5    FastRandom,
6    Exclude(&'a [usize]),
7    RangeList(&'a [(usize, usize)]),
8}
9
10/// * Generates a sorted vector of unique indices for a given size and order, ensuring the specified index is included.
11/// * Calls the subset function to get a subset of indices.
12/// * Replaces an index in the subset with the specified index if it fits the criteria.
13/// * Sorts and returns the result.
14pub fn create_individual_indexes(index: usize, max_index: usize, num_indices: usize) -> Vec<usize> {
15    let mut scratch = vec![0; num_indices];
16    subset(
17        max_index,
18        num_indices,
19        &mut scratch,
20        SubsetMode::StratifiedCorrect,
21    );
22    let mut i = 0;
23    while i < scratch.len() && scratch[i] < index {
24        i += 1;
25    }
26    if i < scratch.len() {
27        scratch[i] = index;
28    }
29
30    scratch.sort_unstable();
31    scratch
32}
33
34pub fn individual_indexes(index: usize, max_index: usize, num_indices: usize, buff: &mut [usize]) {
35    subset(max_index, num_indices, buff, SubsetMode::StratifiedCorrect);
36    let mut i = 0;
37    while i < buff.len() && buff[i] < index {
38        i += 1;
39    }
40    if i < buff.len() {
41        buff[i] = index;
42    }
43
44    buff.sort_unstable();
45}
46/// * Generates a subset of indices of size k from a total of n elements.
47/// * Calls the next function to fill the subset.
48pub fn subset(max_index: usize, num_indicies: usize, buffer: &mut [usize], mode: SubsetMode) {
49    if max_index < num_indicies {
50        panic!("n smaller than k: {} < {}.", max_index, num_indicies);
51    }
52
53    random_provider::with_rng(|rand| match mode {
54        SubsetMode::StratifiedCorrect => {
55            next(max_index, buffer, rand);
56        }
57        SubsetMode::FastRandom => {
58            for i in 0..num_indicies {
59                buffer[i] = rand.range(0..max_index);
60            }
61        }
62        SubsetMode::Exclude(exclude) => {
63            for i in 0..num_indicies {
64                loop {
65                    let index = rand.range(0..max_index);
66                    if !exclude.contains(&index) {
67                        buffer[i] = index;
68                        break;
69                    }
70                }
71            }
72        }
73        SubsetMode::RangeList(range_list) => {
74            for i in 0..num_indicies {
75                let (start, end) = range_list[i % range_list.len()];
76                buffer[i] = rand.range(start..end);
77            }
78        }
79    })
80}
81
82/// * Fills the subset with indices.
83/// * If the subset size equals the total number of elements, it fills the subset with sequential indices.
84/// * Otherwise, it calls build_subset to generate the subset and invert if necessary.
85/// * build_subset Function:
86/// * Constructs a subset of indices using a random selection process.
87/// * Ensures the subset size and range are valid.
88/// * Initializes the subset with evenly spaced indices.
89/// * Adjusts the subset by randomly selecting indices and ensuring they are unique.
90fn next(max_index: usize, sub_set: &mut [usize], rand: &mut RdRand<'_>) {
91    let k = sub_set.len();
92    if k == max_index {
93        for i in 0..k {
94            sub_set[i] = i;
95        }
96        return;
97    }
98    build_subset(max_index, sub_set, rand);
99    if k > max_index - k {
100        invert(max_index, sub_set);
101    }
102}
103
104/// * Inverts the subset to ensure all indices are unique and within the specified range.
105/// * Uses a helper vector to track used indices and fills the subset with the remaining indices.
106fn build_subset(max_index: usize, sub: &mut [usize], rand: &mut RdRand<'_>) {
107    let k = sub.len();
108    check_subset(max_index, k);
109
110    for i in 0..k {
111        sub[i] = i * max_index / k;
112    }
113
114    for _ in 0..k {
115        let mut ix;
116        let mut l;
117        loop {
118            ix = rand.range(1..max_index);
119            l = (ix * k - 1) / max_index;
120            if sub[l] < ix {
121                break;
122            }
123        }
124        sub[l] += 1;
125    }
126
127    let mut ip = 0;
128    let mut is_ = k;
129    for i in 0..k {
130        let m = sub[i];
131        sub[i] = 0;
132        if m != i * max_index / k {
133            ip += 1;
134            sub[ip - 1] = m;
135        }
136    }
137
138    let ihi = ip;
139    for i in 1..=ihi {
140        ip = ihi + 1 - i;
141        let l = 1 + (sub[ip - 1] * k - 1) / max_index;
142        let ids = sub[ip - 1] - (l - 1) * max_index / k;
143        sub[ip - 1] = 0;
144        sub[is_ - 1] = l;
145        is_ -= ids;
146    }
147
148    for ll in 1..=k {
149        let l = k + 1 - ll;
150        if sub[l - 1] != 0 {
151            let ir = l;
152            let m0 = 1 + (sub[l - 1] - 1) * max_index / k;
153            let m = sub[l - 1] * max_index / k - m0 + 1;
154
155            let ix = rand.range(m0..m0 + m - 1);
156            let mut i = l + 1;
157            while i <= ir && ix >= sub[i - 1] {
158                sub[i - 2] = sub[i - 1];
159                i += 1;
160            }
161            sub[i - 2] = ix;
162        }
163    }
164}
165
166/// * Finds the index of a value in a subset.
167/// * Returns the index if found, otherwise returns -1.
168fn invert(n: usize, a: &mut [usize]) {
169    let k = a.len();
170    let mut v = n - 1;
171    let j = n - k - 1;
172    let mut ac = vec![0; k];
173    ac.copy_from_slice(a);
174
175    for i in (0..k).rev() {
176        while let Some(_) = index_of(&ac, j, v) {
177            v -= 1;
178        }
179        a[i] = v;
180        v -= 1;
181    }
182}
183
184fn index_of(a: &[usize], start: usize, value: usize) -> Option<usize> {
185    for i in (0..=start).rev() {
186        if a[i] == value {
187            return Some(i);
188        }
189    }
190
191    None
192}
193
194fn check_subset(n: usize, k: usize) {
195    if n < k {
196        panic!("n smaller than k: {} < {}.", n, k);
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_fast_random_subset() {
206        let n = 50;
207        let k = 20;
208        let mut result = vec![0; k];
209        subset(n, k, &mut result, SubsetMode::FastRandom);
210        assert_eq!(result.len(), k);
211        assert!(result.iter().all(|&x| x < n));
212    }
213
214    #[test]
215    fn test_exclude_subset() {
216        let n = 10;
217        let k = 5;
218        let blacklist = vec![2, 3, 4];
219        let mut result = vec![0; k];
220        subset(n, k, &mut result, SubsetMode::Exclude(&blacklist));
221        assert_eq!(result.len(), k);
222        assert!(result.iter().all(|&x| !blacklist.contains(&x)));
223    }
224
225    #[test]
226    fn test_range_list_subset() {
227        let ranges = vec![(0, 5), (10, 15)];
228        let mut result = vec![0; 6];
229        subset(20, 6, &mut result, SubsetMode::RangeList(&ranges));
230        assert_eq!(result.len(), 6);
231        assert!(
232            result
233                .iter()
234                .all(|&x| (0..5).contains(&x) || (10..15).contains(&x))
235        );
236    }
237
238    #[test]
239    fn test_individual_indexes_includes_index() {
240        let result = create_individual_indexes(7, 20, 5);
241        assert_eq!(result.len(), 5);
242        assert!(result.contains(&7));
243        let mut sorted = result.clone();
244        sorted.sort_unstable();
245        assert_eq!(sorted, result); // must be sorted
246    }
247}