1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5use rand::{Rng, RngExt as _};
6
7pub struct WeightedIndex {
28 weights: Vec<u64>,
30 tree: Vec<u128>,
32 counts: Vec<usize>,
34 total: u128,
36 positive: usize,
38}
39
40impl WeightedIndex {
41 pub fn new(weights: impl IntoIterator<Item = u64>) -> Self {
43 let weights: Vec<_> = weights.into_iter().collect();
44 let mut tree: Vec<_> = weights.iter().copied().map(u128::from).collect();
45 let mut counts: Vec<_> = weights
46 .iter()
47 .map(|&weight| usize::from(weight != 0))
48 .collect();
49 let total = tree.iter().sum();
50 let positive = counts.iter().sum();
51
52 for index in 0..tree.len() {
54 let parent = index | (index + 1);
55 if parent < tree.len() {
56 tree[parent] += tree[index];
57 counts[parent] += counts[index];
58 }
59 }
60 Self {
61 weights,
62 tree,
63 counts,
64 total,
65 positive,
66 }
67 }
68
69 pub const fn total(&self) -> u128 {
71 self.total
72 }
73
74 pub fn weight(&self, index: usize) -> u64 {
80 self.weights[index]
81 }
82
83 pub fn set(&mut self, index: usize, weight: u64) -> u64 {
89 self.update(index, |_| weight)
90 }
91
92 pub fn update(&mut self, index: usize, f: impl FnOnce(u64) -> u64) -> u64 {
99 let entry = &mut self.weights[index];
101 let previous = *entry;
102 let weight = f(previous);
103 if weight == previous {
104 return previous;
105 }
106 *entry = weight;
107
108 let old = u128::from(previous);
110 let new = u128::from(weight);
111 let was_positive = usize::from(previous != 0);
112 let is_positive = usize::from(weight != 0);
113 self.total = self.total - old + new;
114 self.positive = self.positive - was_positive + is_positive;
115 let mut node = index;
116 while node < self.tree.len() {
117 self.tree[node] = self.tree[node] - old + new;
118 if was_positive != is_positive {
119 self.counts[node] = self.counts[node] - was_positive + is_positive;
120 }
121 node |= node + 1;
122 }
123 previous
124 }
125
126 pub fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<usize> {
130 (self.total != 0).then(|| self.locate::<false>(rng.random_range(0..self.total)))
131 }
132
133 pub fn sample_uniform<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<usize> {
137 (self.positive != 0)
138 .then(|| self.locate::<true>(rng.random_range(0..self.positive as u128)))
139 }
140
141 pub const fn without_replacement(&mut self) -> WithoutReplacement<'_> {
166 WithoutReplacement {
167 pool: Pool::Borrowed {
168 index: self,
169 excluded: Vec::new(),
170 },
171 previous: None,
172 }
173 }
174
175 pub const fn into_without_replacement(self) -> WithoutReplacement<'static> {
180 WithoutReplacement {
181 pool: Pool::Owned(self),
182 previous: None,
183 }
184 }
185
186 fn locate<const UNIFORM: bool>(&self, mut draw: u128) -> usize {
187 let mut index = 0;
189 let mut step = 1usize << self.tree.len().ilog2();
190 while step != 0 {
191 let next = index + step;
192 if next <= self.tree.len() {
193 let sum = if UNIFORM {
194 self.counts[next - 1] as u128
195 } else {
196 self.tree[next - 1]
197 };
198 if sum <= draw {
199 draw -= sum;
200 index = next;
201 }
202 }
203 step >>= 1;
204 }
205 index
206 }
207}
208
209enum Pool<'a> {
211 Borrowed {
213 index: &'a mut WeightedIndex,
214 excluded: Vec<(usize, u64)>,
215 },
216 Owned(WeightedIndex),
218}
219
220pub struct WithoutReplacement<'a> {
226 pool: Pool<'a>,
228 previous: Option<usize>,
230}
231
232impl WithoutReplacement<'_> {
233 pub fn is_empty(&self) -> bool {
235 let index = match &self.pool {
236 Pool::Borrowed { index, .. } => &**index,
237 Pool::Owned(index) => index,
238 };
239 index.positive == usize::from(self.previous.is_some())
240 }
241
242 pub fn sample<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Option<usize> {
246 self.draw::<false, R>(rng)
247 }
248
249 pub fn sample_uniform<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Option<usize> {
253 self.draw::<true, R>(rng)
254 }
255
256 fn draw<const UNIFORM: bool, R: Rng + ?Sized>(&mut self, rng: &mut R) -> Option<usize> {
257 if self.is_empty() {
258 return None;
259 }
260 let (index, excluded) = match &mut self.pool {
261 Pool::Borrowed { index, excluded } => (&mut **index, Some(excluded)),
262 Pool::Owned(index) => (index, None),
263 };
264
265 if let Some(previous) = self.previous {
267 if let Some(excluded) = excluded {
268 excluded.push((previous, index.weight(previous)));
269 }
270 self.previous = None;
271 index.set(previous, 0);
272 }
273 self.previous = if UNIFORM {
274 index.sample_uniform(rng)
275 } else {
276 index.sample(rng)
277 };
278 self.previous
279 }
280}
281
282impl Drop for WithoutReplacement<'_> {
283 fn drop(&mut self) {
284 if let Pool::Borrowed { index, excluded } = &mut self.pool {
285 for &(position, weight) in excluded.iter() {
286 index.set(position, weight);
287 }
288 }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::TestRng;
296 use std::panic::{AssertUnwindSafe, catch_unwind};
297
298 const DRAWS: usize = 2_000;
300
301 fn assert_intervals(index: &WeightedIndex, weights: &[u64]) {
304 assert_eq!(index.weights, weights);
305 let mut start = 0;
306 let mut positive = 0;
307 for (position, &weight) in weights.iter().enumerate() {
308 let end = start + u128::from(weight);
309 if weight != 0 {
310 assert_eq!(index.locate::<false>(start), position);
311 assert_eq!(index.locate::<false>(end - 1), position);
312 assert_eq!(index.locate::<true>(positive as u128), position);
313 positive += 1;
314 }
315 start = end;
316 }
317 assert_eq!(index.total(), start);
318 assert_eq!(index.positive, positive);
319 }
320
321 #[test]
322 fn weighted_index_updates_match_intervals() {
323 let mut rng = TestRng::new(0);
324 for len in [
325 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 65, 127, 128, 129,
326 ] {
327 let mut weights: Vec<_> = (0..len).map(|i| [0, 1, u64::MAX][i % 3]).collect();
328 let mut index = WeightedIndex::new(weights.iter().copied());
329 assert_intervals(&index, &weights);
330 for _ in 0..64 {
331 if weights.is_empty() {
332 break;
333 }
334 let position = rng.random_range(0..weights.len());
335 let weight = [0, 1, u64::MAX, rng.random()][rng.random_range(0..4)];
336 assert_eq!(index.set(position, weight), weights[position]);
337 weights[position] = weight;
338 assert_intervals(&index, &weights);
339 }
340 }
341 }
342
343 #[test]
344 fn weighted_index_update_uses_current_weight() {
345 let mut index = WeightedIndex::new([0, 7, u64::MAX]);
347 let mut calls = 0;
348 let previous = index.update(1, |weight| {
349 calls += 1;
350 weight * 2
351 });
352 assert_eq!(previous, 7);
353 assert_eq!(calls, 1);
354 assert_intervals(&index, &[0, 14, u64::MAX]);
355
356 for (position, weight) in [0, 14, u64::MAX].into_iter().enumerate() {
358 assert_eq!(index.update(position, core::convert::identity), weight);
359 }
360 assert_intervals(&index, &[0, 14, u64::MAX]);
361
362 assert_eq!(index.update(2, |_| 0), u64::MAX);
364 assert_eq!(index.update(0, |weight| weight + 1), 0);
365 assert_intervals(&index, &[1, 14, 0]);
366 }
367
368 #[test]
369 fn weighted_index_update_panics_preserve_index() {
370 for weights in [vec![], vec![0, 7, u64::MAX]] {
372 let mut index = WeightedIndex::new(weights.iter().copied());
373 for position in [weights.len(), usize::MAX] {
374 let mut called = false;
375 let result = catch_unwind(AssertUnwindSafe(|| {
376 index.update(position, |_| {
377 called = true;
378 1
379 });
380 }));
381 assert!(result.is_err());
382 assert!(!called);
383 assert_intervals(&index, &weights);
384 }
385 }
386
387 let mut index = WeightedIndex::new([0, 7, u64::MAX]);
389 let result = catch_unwind(AssertUnwindSafe(|| {
390 index.update(1, |_| panic!("callback must not return"));
391 }));
392 assert!(result.is_err());
393 assert_intervals(&index, &[0, 7, u64::MAX]);
394 }
395
396 #[test]
397 fn weighted_index_zero_total_does_not_draw() {
398 let mut rng = TestRng::new(0);
399 let mut reference = TestRng::new(0);
400 for weights in [vec![], vec![0], vec![0; 65]] {
401 let mut index = WeightedIndex::new(weights);
402 assert_eq!(index.sample(&mut rng), None);
403 assert_eq!(index.sample_uniform(&mut rng), None);
404 let mut draws = index.without_replacement();
405 assert!(draws.is_empty());
406 assert_eq!(draws.sample(&mut rng), None);
407 assert_eq!(draws.sample_uniform(&mut rng), None);
408 }
409 assert_eq!(rng.random::<[u64; 4]>(), reference.random::<[u64; 4]>());
410 let mut index = WeightedIndex::new([0, 7, 0]);
411 assert_eq!(index.sample(&mut rng), Some(1));
412 index.set(1, 0);
413 assert_eq!(index.sample(&mut rng), None);
414 }
415
416 #[test]
417 fn uniform_draws_match_unit_weights() {
418 for weights in [vec![0, 1, 0], vec![u64::MAX, 0, 1, 7], vec![9; 65]] {
419 let index = WeightedIndex::new(weights.iter().copied());
420 let uniform = WeightedIndex::new(weights.iter().map(|&weight| u64::from(weight != 0)));
421 let mut rng = TestRng::new(0);
422 let mut reference = TestRng::new(0);
423 for _ in 0..DRAWS {
424 assert_eq!(
425 index.sample_uniform(&mut rng),
426 uniform.sample(&mut reference)
427 );
428 }
429 assert_eq!(rng.random::<[u64; 4]>(), reference.random::<[u64; 4]>());
430 }
431 }
432
433 fn assert_without_replacement(
436 mut draws: WithoutReplacement<'_>,
437 weights: &[u64],
438 stop_after: usize,
439 mode: usize,
440 ) {
441 let mut weighted = WeightedIndex::new(weights.iter().copied());
442 let mut uniform = WeightedIndex::new(weights.iter().map(|&weight| u64::from(weight != 0)));
443 let mut rng = TestRng::new(0);
444 let mut reference = TestRng::new(0);
445 for step in 0..stop_after {
446 assert!(!draws.is_empty());
447 let (actual, expected) = if mode == 1 || (mode == 2 && step % 2 == 1) {
448 (
449 draws.sample_uniform(&mut rng),
450 uniform.sample(&mut reference),
451 )
452 } else {
453 (draws.sample(&mut rng), weighted.sample(&mut reference))
454 };
455 assert_eq!(actual, expected);
456 let selected = expected.unwrap();
457 weighted.set(selected, 0);
458 uniform.set(selected, 0);
459 }
460 assert_eq!(draws.is_empty(), weighted.total() == 0);
461 if draws.is_empty() {
462 assert_eq!(draws.sample(&mut rng), None);
463 assert_eq!(draws.sample_uniform(&mut rng), None);
464 }
465 assert_eq!(rng.random::<[u64; 4]>(), reference.random::<[u64; 4]>());
466 }
467
468 #[test]
469 fn without_replacement_matches_exclusion_and_restores_weights() {
470 for weights in [
471 vec![],
472 vec![0, 0],
473 vec![0, 1, 0],
474 vec![u64::MAX, 1, 0, 7, u64::MAX],
475 vec![1; 65],
476 ] {
477 let positive = weights.iter().filter(|&&weight| weight != 0).count();
478 for stop_after in 0..=positive {
479 for mode in 0..3 {
480 let mut index = WeightedIndex::new(weights.iter().copied());
481 assert_without_replacement(
482 index.without_replacement(),
483 &weights,
484 stop_after,
485 mode,
486 );
487 assert_intervals(&index, &weights);
488 assert_without_replacement(
489 index.into_without_replacement(),
490 &weights,
491 stop_after,
492 mode,
493 );
494 }
495 }
496 }
497 }
498
499 #[test]
500 fn without_replacement_restores_weights_on_unwind() {
501 let weights = [u64::MAX, 1, 0, 7];
502 let mut index = WeightedIndex::new(weights);
503 let result = catch_unwind(AssertUnwindSafe(|| {
504 let mut draws = index.without_replacement();
505 let mut rng = TestRng::new(0);
506 assert!(draws.sample(&mut rng).is_some());
507 assert!(draws.sample_uniform(&mut rng).is_some());
508 panic!("unwind with an unfinished selection");
509 }));
510 assert!(result.is_err());
511 assert_intervals(&index, &weights);
512 }
513
514 fn sample_counts<const N: usize>(weights: [u64; N]) -> [usize; N] {
516 let index = WeightedIndex::new(weights);
517 let mut counts = [0; N];
518 for seed in 0..DRAWS as u64 {
519 counts[index.sample(&mut TestRng::new(seed)).unwrap()] += 1;
520 }
521 counts
522 }
523
524 #[test]
525 fn weighted_index_prefers_heavier_weights() {
526 let counts = sample_counts([0, 9, 1, 0]);
527 assert_eq!(counts[0], 0);
528 assert_eq!(counts[3], 0);
529 assert!(
530 (DRAWS * 85 / 100..DRAWS * 95 / 100).contains(&counts[1]),
531 "{counts:?}"
532 );
533 }
534
535 #[test]
536 fn weighted_index_equal_weights_are_uniform() {
537 let counts = sample_counts([7, 7, 7]);
538 for count in counts {
539 assert!((DRAWS / 4..DRAWS * 5 / 12).contains(&count), "{counts:?}");
540 }
541 }
542}