1#[inline]
15pub fn sparse_intersection_count(a: &[u32], b: &[u32]) -> usize {
16 if a.is_empty() || b.is_empty() {
17 return 0;
18 }
19
20 let (small, large) = if a.len() <= b.len() { (a, b) } else { (b, a) };
22 if small.len() * 8 < large.len() {
25 return galloping_intersection_count(small, large);
26 }
27
28 #[cfg(target_arch = "x86_64")]
29 if let Some(count) = crate::core::simd_math::simd_intersection_count(small, large) {
30 return count;
31 }
32
33 merge_intersection_count(small, large)
34}
35
36#[inline]
38fn merge_intersection_count(a: &[u32], b: &[u32]) -> usize {
39 let mut count = 0;
40 let mut i = 0;
41 let mut j = 0;
42 while i < a.len() && j < b.len() {
43 match a[i].cmp(&b[j]) {
44 std::cmp::Ordering::Less => i += 1,
45 std::cmp::Ordering::Greater => j += 1,
46 std::cmp::Ordering::Equal => {
47 count += 1;
48 i += 1;
49 j += 1;
50 }
51 }
52 }
53 count
54}
55
56#[inline]
62fn galloping_intersection_count(small: &[u32], large: &[u32]) -> usize {
63 let mut count = 0;
64 let mut lo = 0; for &val in small {
66 let mut step = 1;
69 while lo + step < large.len() && large[lo + step] < val {
70 step *= 2;
71 }
72 let hi = (lo + step).min(large.len());
74 lo = binary_search_left(&large[lo..hi], val) + lo;
76 if lo < large.len() && large[lo] == val {
77 count += 1;
78 lo += 1; }
80 }
81 count
82}
83
84#[inline]
86fn binary_search_left(slice: &[u32], target: u32) -> usize {
87 let mut lo = 0;
88 let mut hi = slice.len();
89 while lo < hi {
90 let mid = lo + (hi - lo) / 2;
91 if slice[mid] < target {
92 lo = mid + 1;
93 } else {
94 hi = mid;
95 }
96 }
97 lo
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_sparse_intersection() {
106 let a = vec![1, 3, 5, 7, 9];
107 let b = vec![2, 3, 6, 7, 10];
108 assert_eq!(sparse_intersection_count(&a, &b), 2);
109 }
110
111 #[test]
112 fn test_sparse_intersection_empty() {
113 let a: Vec<u32> = vec![];
114 let b = vec![1, 2, 3];
115 assert_eq!(sparse_intersection_count(&a, &b), 0);
116 }
117
118 #[test]
119 fn test_galloping_skewed() {
120 let small = vec![5, 50, 500];
122 let large: Vec<u32> = (0..1000).collect();
123 assert_eq!(sparse_intersection_count(&small, &large), 3);
124 }
125
126 #[test]
127 fn test_galloping_no_overlap() {
128 let small = vec![1001, 1002, 1003];
129 let large: Vec<u32> = (0..1000).collect();
130 assert_eq!(sparse_intersection_count(&small, &large), 0);
131 }
132
133 #[test]
134 fn test_galloping_all_overlap() {
135 let small = vec![0, 1, 2, 3, 4];
136 let large: Vec<u32> = (0..1000).collect();
137 assert_eq!(sparse_intersection_count(&small, &large), 5);
138 }
139
140 #[test]
141 fn test_merge_balanced() {
142 let a: Vec<u32> = (0..100).step_by(2).collect(); let b: Vec<u32> = (0..100).step_by(3).collect(); let expected = (0..100u32).filter(|x| x % 2 == 0 && x % 3 == 0).count();
145 assert_eq!(sparse_intersection_count(&a, &b), expected);
146 }
147
148 #[test]
149 fn test_identical() {
150 let a: Vec<u32> = (0..50).collect();
151 assert_eq!(sparse_intersection_count(&a, &a), 50);
152 }
153
154 fn naive(a: &[u32], b: &[u32]) -> usize {
155 let set: std::collections::HashSet<u32> = b.iter().copied().collect();
156 a.iter().filter(|x| set.contains(x)).count()
157 }
158
159 struct Lcg(u64);
160 impl Lcg {
161 fn next(&mut self) -> u64 {
162 self.0 = self
163 .0
164 .wrapping_mul(6364136223846793005)
165 .wrapping_add(1442695040888963407);
166 self.0 >> 33
167 }
168 }
169
170 fn sample(rng: &mut Lcg, n: usize, universe: u64) -> Vec<u32> {
172 let mut set = std::collections::BTreeSet::new();
173 while set.len() < n {
174 set.insert((rng.next() % universe) as u32);
175 }
176 set.into_iter().collect()
177 }
178
179 fn check_all(a: &[u32], b: &[u32]) {
180 let want = naive(a, b);
181 assert_eq!(sparse_intersection_count(a, b), want, "dispatch a,b");
182 assert_eq!(sparse_intersection_count(b, a), want, "dispatch b,a");
183 assert_eq!(merge_intersection_count(a, b), want, "merge");
184 let (small, large) = if a.len() <= b.len() { (a, b) } else { (b, a) };
185 assert_eq!(galloping_intersection_count(small, large), want, "gallop");
186 #[cfg(target_arch = "x86_64")]
187 if let Some(c) = crate::core::simd_math::simd_intersection_count(a, b) {
188 assert_eq!(c, want, "avx2");
189 }
190 }
191
192 #[test]
193 fn all_kernels_match_naive_count() {
194 for seed in 0..300u64 {
195 let mut rng = Lcg(seed);
196 let na = (rng.next() % 2001) as usize;
197 let nb = (rng.next() % 2001) as usize;
198 let universe = (na.max(nb) as u64 + 1) * (1 + rng.next() % 8);
200 check_all(
201 &sample(&mut rng, na, universe),
202 &sample(&mut rng, nb, universe),
203 );
204 }
205 }
206
207 #[test]
208 fn kernels_match_on_edge_shapes_and_tails() {
209 let mut rng = Lcg(7);
210 for na in 0..=17usize {
211 for nb in (0..=17usize).chain([64, 65, 600]) {
212 for universe in [40u64, 2000, 1 << 20] {
213 let universe = universe.max(na.max(nb) as u64);
214 check_all(
215 &sample(&mut rng, na, universe),
216 &sample(&mut rng, nb, universe),
217 );
218 }
219 }
220 }
221 let ident = sample(&mut rng, 777, 5000);
222 check_all(&ident, &ident);
223 let evens: Vec<u32> = (0..1000).map(|x| x * 2).collect();
224 let odds: Vec<u32> = (0..1000).map(|x| x * 2 + 1).collect();
225 check_all(&evens, &odds);
226 check_all(&[], &odds);
227 check_all(&[], &[]);
228 }
229
230 #[test]
231 fn test_reversed_args() {
232 let a = vec![1, 5, 10];
234 let b: Vec<u32> = (0..1000).collect();
235 assert_eq!(
236 sparse_intersection_count(&a, &b),
237 sparse_intersection_count(&b, &a)
238 );
239 }
240}