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