weighted_list/weighted_list.rs
1use std::collections::{ HashSet };
2use std::fmt::{ Debug, Display };
3use std::hash::{ Hash };
4
5use bon::bon;
6use itertools::Itertools;
7use num_traits as nums;
8use rand::prelude::*;
9use rand::seq::{ SliceRandom };
10
11use crate::*;
12
13
14/// A shorthand for [`WeightedList`].
15///
16/// If you refer to [`WeightedList`] prolifically in your code, you may wish to use this for brevity. Otherwise, the full [`WeightedList`] is recommended for clarity.
17pub type WList<V,W> = WeightedList<V,W>;
18
19
20/// A homogeneous list of weighted items with values of type `V` and weights of numerical type `W`.
21///
22/// Near-identical to `Vec<T>`, but stores [`WeightedItem<V,W>`](WeightedItem) objects instead. You can think of it like a `Vec<WeightedItem<V,W>>`.
23///
24/// # Usage
25///
26/// ```
27/// # use weighted_list::*;
28/// let wl: WeightedList<String, u32> = wlist![
29/// (2, "sup".to_string()),
30/// (3, "nova".to_string()),
31/// (5, "shard".to_string()),
32/// ];
33///
34/// for item in &wl {
35/// println!("{item}");
36/// }
37///
38/// if let Some(result) = wl.select_random_value(&mut rand::rng()) {
39/// println!("{result}");
40/// }
41/// ```
42///
43/// # Indexing
44///
45/// `WeightedList` uses *weighted* indexing; this is the key difference between it and a `Vec`. It's most easily explained with an example:
46///
47/// ```should_panic
48/// # use weighted_list::*;
49/// let wl = wlist![(1, "qi"), (2, "sup"), (5, "shard")];
50///
51/// let _ = wl[0]; // => WeightedItem { weight: 1, value: "qi" }
52/// let _ = wl[1]; // => WeightedItem { weight: 2, value: "sup" }
53/// let _ = wl[2]; // => WeightedItem { weight: 2, value: "sup" }
54/// let _ = wl[3]; // => WeightedItem { weight: 5, value: "shard" }
55/// let _ = wl[4]; // => WeightedItem { weight: 5, value: "shard" }
56/// let _ = wl[5]; // => WeightedItem { weight: 5, value: "shard" }
57/// let _ = wl[6]; // => WeightedItem { weight: 5, value: "shard" }
58/// let _ = wl[7]; // => WeightedItem { weight: 5, value: "shard" }
59/// let _ = wl[8]; // => panic - out of bounds!
60/// ```
61///
62/// In essence, each value is "copied" a number of times equal to its weight – this is what enables the weighted randomisation. But because the values are stored in [`WeightedItem`] objects, instead of actually being duplicated, larger weight values can be used without fear of performance impacts.
63///
64/// # Tips
65///
66/// - If you only need integer weights, use unsigned types like `u32` to enforce non-negative item weights.
67/// - If you really want to, you can use `NonZero<u32>` to further ensure item weights are valid.
68/// - Most methods return `&Self` or `&mut Self`, allowing you to chain methods. Here's a contrived example:
69///
70/// ```
71/// # use weighted_list::*;
72/// let mut list = wlist![(2, "sup")];
73///
74/// list.push_value("sup")
75/// .merge_duplicates()
76/// .prune()
77/// .len();
78/// ```
79#[derive(Clone, Hash, PartialEq, Eq, Default, Debug)]
80pub struct WeightedList<V, W: Weight>
81{
82 data: Vec<WeightedItem<V,W>>
83}
84
85// == CONSTRUCTORS == //
86/// Methods for constructing a [`WeightedList`].
87impl<V, W: Weight> WeightedList<V,W>
88{
89 /// Construct an empty list.
90 pub fn new() -> Self
91 {
92 Self { data: Vec::new() }
93 }
94
95 /// Construct an empty list with the specified capacity.
96 pub fn with_capacity(capacity: usize) -> Self
97 {
98 Self { data: Vec::with_capacity(capacity) }
99 }
100
101 /// Construct a [`WeightedList`] from an iterable of `value`s, merging duplicate values into single [`WeightedItem`]s.
102 ///
103 /// Note that this has $O(n^2)$ time complexity.
104 pub fn from_expanded<I>(values: I) -> Self
105 where
106 I: IntoIterator<Item = V>,
107 V: PartialEq,
108 {
109 let mut out = WeightedList::new();
110
111 for value in values {
112 if let Some(existing) = out.iter_mut().find(|item| item.value == value) {
113 existing.weight += W::one();
114 }
115 else {
116 out.push_value(value);
117 }
118 }
119
120 out
121 }
122}
123
124/// Construct a [`WeightedList`] from the provided `(weight, value)` pairs.
125///
126/// # Usage
127///
128/// ```
129/// # use weighted_list::*;
130/// let wl = wlist![
131/// (2, "sup"),
132/// (3, "nova"),
133/// (5, "shard"),
134/// ];
135///
136/// let empty: WeightedList<(), usize> = wlist![];
137/// ```
138#[macro_export]
139macro_rules! wlist {
140 () => { WeightedList::new() };
141
142 ($( ($weight:expr, $value:expr) ),* $(,)?) =>
143 {
144 WeightedList::from_iter(
145 [
146 $( ($weight, $value), )*
147 ].into_iter()
148 )
149 };
150}
151
152// == CONVERSIONS FROM == //
153impl<V, W: Weight> FromIterator<(W,V)> for WeightedList<V,W> {
154 fn from_iter<I>(pairs: I) -> Self
155 where I: IntoIterator<Item = (W,V)>
156 {
157 Self {
158 data:
159 pairs.into_iter()
160 .map(
161 |(weight, value)| WeightedItem::new(weight, value)
162 )
163 .collect::<Vec<_>>()
164 }
165 }
166}
167impl<V, W: Weight> FromIterator<WeightedItem<V,W>> for WeightedList<V,W>
168{
169 fn from_iter<I>(items: I) -> Self
170 where I: IntoIterator<Item = WeightedItem<V,W>>
171 {
172 let mut data = vec![];
173
174 for item in items {
175 data.push(item);
176 }
177
178 Self { data }
179 }
180}
181
182impl<V, W: Weight> From<Vec<(W,V)>> for WeightedList<V,W> {
183 fn from(pairs: Vec<(W,V)>) -> Self {
184 pairs.into_iter().collect()
185 }
186}
187impl<V, W: Weight> From<Vec<WeightedItem<V,W>>> for WeightedList<V,W> {
188 fn from(data: Vec<WeightedItem<V,W>>) -> Self {
189 Self { data }
190 }
191}
192
193impl<V, W: Weight, const N: usize> From<[(W,V); N]> for WeightedList<V,W> {
194 fn from(pairs: [(W,V); N]) -> Self {
195 pairs.into_iter().collect()
196 }
197}
198impl<V, W: Weight, const N: usize> From<[WeightedItem<V,W>; N]> for WeightedList<V,W> {
199 fn from(pairs: [WeightedItem<V,W>; N]) -> Self {
200 pairs.into_iter().collect()
201 }
202}
203
204// == CONVERSIONS TO == //
205impl<V, W: Weight> From<WeightedList<V,W>> for Vec<WeightedItem<V,W>> {
206 fn from(list: WeightedList<V,W>) -> Self {
207 list.data
208 }
209}
210
211impl<V, W: Weight> AsRef<Vec<WeightedItem<V,W>>> for WeightedList<V,W> {
212 fn as_ref(&self) -> &Vec<WeightedItem<V,W>> {
213 &self.data
214 }
215}
216impl<V, W: Weight> AsRef<[WeightedItem<V,W>]> for WeightedList<V,W> {
217 fn as_ref(&self) -> &[WeightedItem<V,W>] {
218 &self.data
219 }
220}
221
222impl<V, W: Weight> AsMut<Vec<WeightedItem<V,W>>> for WeightedList<V,W> {
223 fn as_mut(&mut self) -> &mut Vec<WeightedItem<V,W>> {
224 &mut self.data
225 }
226}
227impl<V, W: Weight> AsMut<[WeightedItem<V,W>]> for WeightedList<V,W> {
228 fn as_mut(&mut self) -> &mut [WeightedItem<V,W>] {
229 &mut self.data
230 }
231}
232
233impl<V, W: Weight> std::ops::Deref for WeightedList<V,W> {
234 type Target = [WeightedItem<V,W>];
235
236 fn deref(&self) -> &Self::Target {
237 self.data.deref()
238 }
239}
240impl<V, W: Weight> std::ops::DerefMut for WeightedList<V,W> {
241 fn deref_mut(&mut self) -> &mut Self::Target {
242 self.data.deref_mut()
243 }
244}
245
246// == TRAIT IMPLEMENTATIONS == //
247impl<V, W: Weight> Extend<WeightedItem<V,W>> for WeightedList<V,W>
248{
249 fn extend<T>(&mut self, iter: T)
250 where T: IntoIterator<Item = WeightedItem<V,W>>
251 {
252 for item in iter {
253 self.push_item(item);
254 }
255 }
256}
257
258impl<V, W: Weight> Display for WeightedList<V,W>
259 where
260 V: Display,
261 W: Display,
262{
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
264 {
265 write!(f,
266 "WeightedList[{}]",
267 self.data.iter().map(|item| item.to_string()).join(", ")
268 )
269 }
270}
271
272// == INDEXING == //
273impl<V, W: Weight> std::ops::Index<W> for WeightedList<V,W>
274{
275 type Output = WeightedItem<V,W>;
276
277 fn index(&self, weighted_index: W) -> &Self::Output
278 {
279 let mut t = W::zero();
280
281 for item in &self.data {
282 t += item.weight;
283
284 if t > weighted_index {
285 return item;
286 }
287 };
288
289 panic!(
290 "index out of bounds: the len is {:?} but the index is {:?}",
291 self.len(), weighted_index
292 );
293 }
294}
295
296impl<V, W: Weight> std::ops::IndexMut<W> for WeightedList<V,W>
297{
298 fn index_mut(&mut self, weighted_index: W) -> &mut Self::Output
299 {
300 let idx = self._unweight_index_(weighted_index);
301 &mut self.data[idx]
302 }
303}
304
305// == ITERATION == //
306impl<V, W: Weight> IntoIterator for WeightedList<V,W>
307{
308 type Item = WeightedItem<V,W>;
309 type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
310
311 /// ```compile_fail
312 /// # use weighted_list::*;
313 /// let list = wlist![]
314 /// for _ in list {}
315 /// list; // compile error
316 /// ```
317 fn into_iter(self) -> Self::IntoIter {
318 self.data.into_iter()
319 }
320}
321
322impl<'l, V, W: Weight> IntoIterator for &'l WeightedList<V,W>
323{
324 type Item = &'l WeightedItem<V,W>;
325 type IntoIter = std::slice::Iter<'l, WeightedItem<V,W>>;
326
327 fn into_iter(self) -> Self::IntoIter {
328 self.data.iter()
329 }
330}
331
332impl<'l, V, W: Weight> IntoIterator for &'l mut WeightedList<V,W>
333{
334 type Item = &'l mut WeightedItem<V,W>;
335 type IntoIter = std::slice::IterMut<'l, WeightedItem<V,W>>;
336
337 fn into_iter(self) -> Self::IntoIter {
338 self.data.iter_mut()
339 }
340}
341
342// == ACCESSORS == //
343/// Methods for accessing data of the list.
344impl<V, W: Weight> WeightedList<V,W>
345{
346 /// Get an iterator over the weights of each item in the list.
347 ///
348 /// # Usage
349 ///
350 /// ```
351 /// # use weighted_list::*;
352 /// let wl = wlist![(2, "sup"), (3, "nova")];
353 ///
354 /// for weight in wl.weights() {
355 /// println!("{weight}"); // => 2, 3
356 /// }
357 ///
358 /// let weights = wl.weights().collect::<Vec<u32>>();
359 /// assert_eq!(weights, vec![2, 3]);
360 /// ```
361 pub fn weights(&self) -> impl Iterator<Item = W>
362 {
363 self.data.iter().map(|item| item.weight)
364 }
365
366 /// Get an iterator over the values of each item in the list.
367 ///
368 /// # Usage
369 ///
370 /// ```
371 /// # use weighted_list::*;
372 /// let wl = wlist![(2, "sup"), (3, "nova")];
373 ///
374 /// for value in wl.values() {
375 /// println!("{value}"); // => &"sup", &"nova"
376 /// }
377 ///
378 /// let values = wl.values().collect::<Vec<&&str>>();
379 /// assert_eq!(values, vec![&"sup", &"nova"]);
380 /// ```
381 pub fn values(&self) -> impl Iterator<Item = &V>
382 {
383 self.data.iter().map(|item| &item.value)
384 }
385
386 /// Get a vector of references to items in the list.
387 ///
388 /// # Usage
389 ///
390 /// ```
391 /// # use weighted_list::*;
392 /// let wl = wlist![(2, "sup"), (3, "nova")];
393 /// let items = wl.items();
394 ///
395 /// assert_eq!(items[0].value, "sup");
396 /// assert_eq!(items[1].value, "nova");
397 /// ```
398 ///
399 /// # Tips
400 ///
401 /// - This may be helpful for [non-weighted indexing](WeightedList#indexing).
402 /// - If you'd like a vector of owned [`WeightedItem`](WeightedItem)s, [`WeightedList`](WeightedList) implements `Into<Vec>`:
403 ///
404 /// ```rust
405 /// # use weighted_list::*;
406 /// let wl = wlist![(2, "sup"), (3, "nova")];
407 /// let items = Vec::from(wl);
408 ///
409 /// assert_eq!(items[0].value, "sup");
410 /// assert_eq!(items[1].value, "nova");
411 /// ```
412 pub fn items(&self) -> Vec<&WeightedItem<V,W>>
413 {
414 self.data.iter().collect_vec()
415 }
416
417 /// Get an iterator over (weight, value) tuples representing each item in the list.
418 ///
419 /// # Usage
420 ///
421 /// ```
422 /// # use weighted_list::*;
423 /// let wl = wlist![(2, "sup"), (3, "nova")];
424 ///
425 /// for (weight, value) in wl.raw() {
426 /// println!("({weight}, {value})"); // => (2, "sup"), (3, "nova")
427 /// }
428 ///
429 /// let raw = wl.raw().collect::<Vec<_>>();
430 /// assert_eq!(raw, vec![(2, &"sup"), (3, &"nova")]);
431 /// ```
432 ///
433 /// # Notes
434 ///
435 /// This function acts essentially like an "un-constructor", giving back the tuples you usually use to construct a [`WeightedList`]:
436 ///
437 /// ```
438 /// # use weighted_list::*;
439 /// let wl = WeightedList::from([(2, "sup"), (3, "nova")]);
440 /// let rl = wl.raw(); // => [(2, &"sup"), (3, &"nova")]
441 /// ```
442 pub fn raw(&self) -> impl Iterator<Item = (W,&V)>
443 {
444 self.data.iter().map(|item| (item.weight, &item.value))
445 }
446
447 /// Get an iterator over each value in the list, repeated a number of times equal to its weight.
448 ///
449 /// # Usage
450 ///
451 /// ```
452 /// # use weighted_list::*;
453 /// let wl = wlist![(2, "sup"), (3, "nova")];
454 ///
455 /// let mut test = vec![];
456 /// for value in wl.expanded() {
457 /// test.push(*value);
458 /// }
459 ///
460 /// assert_eq!(test, vec!["sup", "sup", "nova", "nova", "nova"]);
461 /// ```
462 ///
463 /// # Notes
464 ///
465 /// - Since repeating an entity a non-integer number of times is undefined, this requires `W` to be an integer type.
466 pub fn expanded(&self) -> impl Iterator<Item = &V>
467 where W: nums::PrimInt
468 {
469 self.data
470 .iter()
471 .flat_map(|item| std::iter::repeat_n(
472 &item.value,
473 nums::cast::<W, usize>(item.weight).unwrap_or(0)
474 ))
475 }
476
477 /// Get a vector of the weights of each item in the list.
478 ///
479 /// # Usage
480 ///
481 /// ```
482 /// # use weighted_list::*;
483 /// let wl = wlist![(2, "sup"), (3, "nova")];
484 ///
485 /// let weights = wl.collect_weights();
486 ///
487 /// assert_eq!(weights[0], 2);
488 /// assert_eq!(weights[1], 3);
489 /// ```
490 pub fn collect_weights(&self) -> Vec<W>
491 {
492 self.weights().collect_vec()
493 }
494
495 /// Get a vector of the values of each item in the list.
496 ///
497 /// # Usage
498 ///
499 /// ```
500 /// # use weighted_list::*;
501 /// let wl = wlist![(2, "sup"), (3, "nova")];
502 ///
503 /// let weights = wl.collect_values();
504 ///
505 /// assert_eq!(weights[0], &"sup");
506 /// assert_eq!(weights[1], &"nova");
507 /// ```
508 pub fn collect_values(&self) -> Vec<&V>
509 {
510 self.values().collect_vec()
511 }
512}
513
514// == PROPERTIES == //
515/// Methods for computing properties of the list.
516impl<V, W: Weight> WeightedList<V,W>
517{
518 /// Sum the weights of all items in the list.
519 ///
520 /// # Notes
521 /// - This is not the number of items in the list – use [`.total_items()`](Self::total_items) for that.
522 /// - `self.len() == 0` does not imply the list is empty – items may have zero or negative weights! To check if the list is empty, use [`.is_empty()`](Self::is_empty) instead.
523 pub fn len(&self) -> W
524 {
525 self.data.iter().map(|item| item.weight).sum()
526 }
527
528 pub fn capacity(&self) -> usize
529 {
530 self.data.capacity()
531 }
532
533 /// How many items/values are in the list?
534 ///
535 /// Note that this is not equivalent to [`self.len()`](Self::len), which is the total weights of all items in the list.
536 ///
537 /// # Usage
538 ///
539 /// ```
540 /// # use weighted_list::*;
541 /// let wl = wlist![(2, "sup"), (3, "nova")];
542 ///
543 /// assert_eq!(wl.total_items(), 2);
544 /// assert_eq!(wl.len(), 5);
545 /// ```
546 pub fn total_items(&self) -> usize
547 {
548 self.data.len()
549 }
550
551 /// Does the list contain no items?
552 ///
553 /// # Usage
554 ///
555 /// ```
556 /// # use weighted_list::*;
557 /// let empty: WeightedList<(), usize> = wlist![];
558 /// assert_eq!(empty.is_empty(), true);
559 ///
560 /// assert_eq!(wlist![(0, "qi")].is_empty(), false);
561 /// ```
562 ///
563 /// # Notes
564 /// - [`self.len()`](Self::len) returning `0` does not guarantee `self.is_empty()`, since items could have zero or negative weights. This method only returns `true` if no items are in the list at all.
565 pub fn is_empty(&self) -> bool
566 {
567 self.data.is_empty()
568 }
569
570 /// Do all items have a weight of `0`?
571 ///
572 /// # Usage
573 ///
574 /// ```
575 /// # use weighted_list::*;
576 /// assert_eq!(wlist![(0, "qi")].is_zero(), true);
577 /// assert_eq!(wlist![(1, "qi")].is_zero(), false);
578 /// assert_eq!(wlist![(0, "qi"), (0, "xi")].is_zero(), true);
579 /// assert_eq!(wlist![(0, "qi"), (1, "vi")].is_zero(), false);
580 ///
581 /// let empty: WeightedList<(), usize> = wlist![];
582 /// assert_eq!(empty.is_zero(), true);
583 /// ```
584 ///
585 /// # Notes
586 /// - Returns `true` if `.is_empty()` is `true`.
587 pub fn is_zero(&self) -> bool
588 {
589 self.is_empty()
590 || self.data.iter().all(|item| item.weight == W::zero())
591 }
592
593 /// Do any items have a negative weight?
594 ///
595 /// # Usage
596 ///
597 /// ```
598 /// # use weighted_list::*;
599 /// assert_eq!(wlist![(0, "qi"), ( 2, "sup") ].has_negative_weights(), false);
600 /// assert_eq!(wlist![(0, "qi"), (-1, "aleph")].has_negative_weights(), true);
601 /// ```
602 ///
603 /// # Notes
604 /// - Returns `false` if the list is empty.
605 /// - If you only need integer weights, you should probably use an unsigned type like `u32` to ensure weights are never negative.
606 pub fn has_negative_weights(&self) -> bool
607 {
608 self.data.iter().any(|item| item.weight < W::zero())
609 }
610}
611
612// == LIST QUERYING == //
613/// Methods specialised from `Vec<>` for querying the list.
614impl<V, W: Weight> WeightedList<V,W>
615 where
616 V: Clone
617{
618 /// Return a clone of the list with items sorted in ascending order of weights.
619 ///
620 /// Orderings of items with equivalent weights is (currently) undefined behaviour.
621 pub fn sorted(&self) -> Self
622 where V: Eq, W: Ord
623 {
624 let mut out = self.clone();
625 out.sort();
626 out
627 }
628
629 /// Return a clone of the list with items reversed.
630 pub fn reversed(&self) -> Self
631 {
632 let mut out = self.clone();
633 out.reverse();
634 out
635 }
636}
637
638// == LIST MUTATION == //
639/// Methods specialised from `Vec<>` for mutating the list.
640impl<V, W: Weight> WeightedList<V,W>
641{
642 pub fn reserve(&mut self, additional: usize) -> &mut Self
643 {
644 self.data.reserve(additional);
645 self
646 }
647
648 /// Append an item to the end of the list.
649 ///
650 /// # Usage
651 ///
652 /// ```
653 /// # use weighted_list::*;
654 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
655 ///
656 /// assert_eq!(
657 /// *wl.push_item(wl[0].clone()),
658 /// wlist![(2, "sup"), (3, "nova"), (5, "shard"), (2, "sup")],
659 /// )
660 /// ```
661 pub fn push_item(&mut self, item: WeightedItem<V,W>) -> &mut Self
662 {
663 self.data.push(item);
664 self
665 }
666
667 /// Append a new item with `value` and `weight` to the end of the list.
668 ///
669 /// # Usage
670 ///
671 /// ```
672 /// # use weighted_list::*;
673 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
674 ///
675 /// assert_eq!(
676 /// *wl.push_new_item(7, "cortex"),
677 /// wlist![(2, "sup"), (3, "nova"), (5, "shard"), (7, "cortex")],
678 /// )
679 /// ```
680 pub fn push_new_item(&mut self, weight: W, value: V) -> &mut Self
681 {
682 self.push_item(WeightedItem { weight, value })
683 }
684
685 /// Append a new item with `value` and a weight of `1` to the end of the list.
686 ///
687 /// # Usage
688 ///
689 /// ```
690 /// # use weighted_list::*;
691 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
692 ///
693 /// assert_eq!(
694 /// *wl.push_value("elysion"),
695 /// wlist![(2, "sup"), (3, "nova"), (5, "shard"), (1, "elysion")],
696 /// )
697 /// ```
698 pub fn push_value(&mut self, value: V) -> &mut Self
699 {
700 self.push_item(WeightedItem::unit(value))
701 }
702
703 /// Insert an item into the list at `weighted_index`. If `weighted_index >= len`, the item is appended to the end (the function does *not* panic).
704 pub fn insert_item(&mut self,
705 weighted_index: W,
706 item: WeightedItem<V,W>
707 ) -> &mut Self
708 {
709 self.data.insert(self._unweight_index_nopanic_(weighted_index), item);
710 self
711 }
712
713 /// Insert a new item with `value` and `weight` into the list at `weighted_index`. If `weighted_index >= len`, the item is appended to the end (the function does *not* panic).
714 pub fn insert_new_item(&mut self,
715 weighted_index: W,
716 (weight, value): (W, V)
717 ) -> &mut Self
718 {
719 self.insert_item(weighted_index, WeightedItem::new(weight, value))
720 }
721
722 /// Insert an item with `value` and a weight of `1` into the list at `weighted_index`. If `weighted_index >= len`, the item is appended to the end (the function does *not* panic).
723 pub fn insert_value(&mut self,
724 weighted_index: W,
725 value: V,
726 ) -> &mut Self
727 {
728 self.insert_item(weighted_index, WeightedItem::unit(value))
729 }
730
731 /// Move all items in `other` into `self`, leaving `other` empty.
732 pub fn append(&mut self, other: &mut WeightedList<V,W>) -> &mut Self
733 {
734 self.data.append(&mut other.data);
735 self
736 }
737
738 /// Reverse the order of items in the list (in-place).
739 pub fn reverse(&mut self) -> &mut Self
740 {
741 self.data.reverse();
742 self
743 }
744
745 /// Swap the items at weighted indices `left` and `right`.
746 ///
747 /// # Panics
748 ///
749 /// Panics if `left` or `right` are out of bounds.
750 pub fn swap(&mut self, left: W, right: W) -> &mut Self
751 {
752 let l = self._unweight_index_(left);
753 let r = self._unweight_index_(right);
754 self.data.swap(l, r);
755 self
756 }
757
758 /// Removes the last item from the list and returns it, or `None` if the list is empty.
759 pub fn pop(&mut self) -> Option<WeightedItem<V,W>>
760 {
761 self.data.pop()
762 }
763
764 pub fn pop_if(&mut self,
765 predicate: impl FnOnce(&mut WeightedItem<V,W>) -> bool
766 ) -> Option<WeightedItem<V,W>>
767 {
768 self.data.pop_if(predicate)
769 }
770
771 /// Remove the entire item at `weighted_index` and return it.
772 ///
773 /// # Panics
774 ///
775 /// Panics if `weighted_index` is out of bounds.
776 pub fn remove_at(&mut self, weighted_index: W) -> WeightedItem<V,W>
777 {
778 self.data.remove(self._unweight_index_(weighted_index))
779 }
780
781 /// Remove elements from the end of the list, such that [`self.len()`](Self::len) == `len`. The last element may have its weight decreased.
782 ///
783 /// # Usage
784 ///
785 /// ```rust
786 /// # use weighted_list::*;
787 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
788 ///
789 /// wl.truncate(5);
790 /// assert_eq!(wl, wlist![(2, "sup"), (3, "nova")]);
791 ///
792 /// wl.truncate(3);
793 /// assert_eq!(wl, wlist![(2, "sup"), (1, "nova")]);
794 ///
795 /// wl.truncate(0);
796 /// assert_eq!(wl, wlist![]);
797 /// ```
798 pub fn truncate(&mut self, len: W) -> &mut Self
799 where W: Debug
800 {
801 if len == W::zero() {
802 return self.clear();
803 }
804
805 let mut t = W::zero();
806 let mut n = 0;
807
808 for (i, each) in self.iter_mut().enumerate() {
809 t += each.weight;
810
811 if t >= len {
812 if t > len {
813 each.weight -= t - len;
814 }
815
816 n = i + 1;
817 break;
818 }
819 }
820
821 self.data.truncate(n);
822
823 self
824 }
825
826 /// Retain only items that fulfil `predicate``.
827 pub fn retain<F>(&mut self, predicate: F) -> &mut Self
828 where F: FnMut(&WeightedItem<V,W>) -> bool
829 {
830 self.data.retain(predicate);
831 self
832 }
833
834 /// Retain only items that fulfil `predicate``, passing a mutable reference to the predicate.
835 pub fn retain_mut<F>(&mut self, predicate: F) -> &mut Self
836 where F: FnMut(&mut WeightedItem<V,W>) -> bool
837 {
838 self.data.retain_mut(predicate);
839 self
840 }
841
842 /// Clear the list, removing all items (in-place).
843 ///
844 /// If you'd like to set the weights of all items to `0`, you can use [`.zero_all_weights()`](Self::zero_all_weights).
845 pub fn clear(&mut self) -> &mut Self
846 {
847 self.data.clear();
848 self
849 }
850}
851
852// == SPECIALISED QUERYING == //
853/// Special [`WeightedList`]-specific methods for querying the list.
854impl<V, W: Weight> WeightedList<V,W>
855{
856 /// Does any item in the list have a value equal to `value`?
857 pub fn contains_value(&self, value: &V) -> bool
858 where V: PartialEq
859 {
860 self.data.iter().any(|item| item.value == *value)
861 }
862
863 /// Does any item in the list have a weight equal to `weight`?
864 pub fn contains_weight(&self, weight: W) -> bool
865 {
866 self.data.iter().any(|item| item.weight == weight)
867 }
868}
869
870// == SPECIALISED MUTATION == //
871/// Special [`WeightedList`]-specific methods for mutating the list.
872impl<V, W: Weight> WeightedList<V,W>
873{
874 /// Remove all items with non-positive weight.
875 pub fn prune(&mut self) -> &mut Self
876 {
877 self.data.retain(|item| item.weight > W::zero());
878 self
879 }
880
881 /// Return a clone of the list with all items having a non-positive weight removed.
882 ///
883 /// Out-of-place version of [`.prune()`](Self::prune).
884 pub fn pruned(&self) -> Self
885 where V: Clone
886 {
887 let mut out = self.clone();
888 out.prune();
889 out
890 }
891
892 /// Find the first occurrence (from the left) of an item with `value`, and remove the entire item.
893 ///
894 /// # Usage
895 ///
896 /// ```
897 /// # use weighted_list::*;
898 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
899 ///
900 /// assert_eq!(
901 /// *wl.remove_value_first(&"nova"),
902 /// wlist![(2, "sup"), (5, "shard")],
903 /// )
904 /// ```
905 pub fn remove_value_first(&mut self, value: &V) -> &mut Self
906 where V: PartialEq
907 {
908 self.remove_first_where(|item| item.value == *value)
909 }
910
911 /// Find the last occurrence (from the right) of an item with `value`, and remove the entire item.
912 ///
913 /// # Usage
914 ///
915 /// ```
916 /// # use weighted_list::*;
917 /// let mut wl = wlist![(0, "qi"), (1, "qi"), (2, "sup")];
918 ///
919 /// assert_eq!(
920 /// *wl.remove_value_last(&"qi"),
921 /// wlist![(0, "qi"), (2, "sup")],
922 /// )
923 /// ```
924 pub fn remove_value_last(&mut self, value: &V) -> &mut Self
925 where V: PartialEq
926 {
927 self.remove_last_where(|item| item.value == *value)
928 }
929
930 /// Find the first occurrence (from the left) of an item that fulfils `predicate`, and remove the entire item.
931 ///
932 /// # Usage
933 ///
934 /// ```
935 /// # use weighted_list::*;
936 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
937 ///
938 /// assert_eq!(
939 /// *wl.remove_first_where(|item| item.weight > 2),
940 /// wlist![(2, "sup"), (5, "shard")],
941 /// )
942 /// ```
943 pub fn remove_first_where<F>(&mut self, predicate: F) -> &mut Self
944 where F: FnMut(&WeightedItem<V,W>) -> bool
945 {
946 if let Some(idx) = self.iter().position(predicate) {
947 self.data.remove(idx);
948 }
949
950 self
951 }
952
953 /// Find the last occurrence (from the right) of an item that fulfils `predicate`, and remove the entire item.
954 ///
955 /// # Usage
956 ///
957 /// ```
958 /// # use weighted_list::*;
959 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
960 ///
961 /// assert_eq!(
962 /// *wl.remove_last_where(|item| item.weight > 2),
963 /// wlist![(2, "sup"), (3, "nova")],
964 /// );
965 /// ```
966 pub fn remove_last_where<F>(&mut self, predicate: F) -> &mut Self
967 where F: FnMut(&WeightedItem<V,W>) -> bool
968 {
969 if let Some(idx) = self.iter().rposition(predicate) {
970 self.data.remove(idx);
971 }
972
973 self
974 }
975
976 /// Set the weight of all items to `0`.
977 ///
978 /// # Usage
979 ///
980 /// ```
981 /// # use weighted_list::*;
982 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
983 ///
984 /// assert_eq!(
985 /// *wl.zero_all_weights(),
986 /// wlist![(0, "sup"), (0, "nova"), (0, "shard")],
987 /// )
988 /// ```
989 pub fn zero_all_weights(&mut self) -> &mut Self
990 {
991 for item in &mut self.data {
992 item.weight = W::zero();
993 }
994
995 self
996 }
997
998 /// Set the weight of all items to `weight`.
999 ///
1000 /// # Usage
1001 ///
1002 /// ```
1003 /// # use weighted_list::*;
1004 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1005 ///
1006 /// assert_eq!(
1007 /// *wl.set_all_weights(1),
1008 /// wlist![(1, "sup"), (1, "nova"), (1, "shard")],
1009 /// )
1010 /// ```
1011 pub fn set_all_weights(&mut self, weight: W) -> &mut Self
1012 {
1013 for item in &mut self.data {
1014 item.weight = weight;
1015 }
1016
1017 self
1018 }
1019
1020 /// Return a clone of the list with all item weights normalised such that they sum to `1.0`.
1021 ///
1022 /// # Usage
1023 ///
1024 /// ```
1025 /// # use weighted_list::*;
1026 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1027 ///
1028 /// assert_eq!(
1029 /// wl.normalised().ok(),
1030 /// Some(wlist![(0.2, "sup"), (0.3, "nova"), (0.5, "shard")])
1031 /// );
1032 /// ```
1033 pub fn normalised(&mut self) -> Result<WeightedList<V, f64>, &str>
1034 where V: Clone
1035 {
1036 let total;
1037
1038 if let Some(t) = nums::cast::<W, f64>(self.len()) {
1039 total = t;
1040 } else {
1041 return Err("Error casting total weights to `f64`");
1042 };
1043
1044 let items =
1045 self.data
1046 .iter()
1047 .map(|item| {
1048 let weight = nums::cast::<W, f64>(item.weight)?;
1049 Some(WeightedItem {
1050 weight: weight / total,
1051 value: item.value.clone()
1052 })
1053 })
1054 .collect::<Option<Vec<WeightedItem<V, f64>>>>()
1055 ;
1056
1057 match items {
1058 Some(data) => Ok(WeightedList { data }),
1059 None => Err("Error casting weights to `f64`")
1060 }
1061 }
1062}
1063
1064/// Methods for merging items into the list.
1065///
1066/// This involves comparing item values to check for duplicates, hence requiring `V: PartialEq`.
1067impl<V, W: Weight> WeightedList<V,W>
1068 where
1069 V: PartialEq
1070{
1071 /// Merge an item into the list. If an item with the same value already exists, add the weight of the new item to the existing item. Otherwise, append the new item to the list.
1072 ///
1073 /// # Tips
1074 ///
1075 /// - Use this method when you already have an existing [`WeightedItem`] instance. If you're going to construct a new [`WeightedItem`], [`.merge_new_item()`](Self::merge_new_item) is probably more convenient.
1076 ///
1077 /// # Usage
1078 ///
1079 /// ```
1080 /// # use weighted_list::*;
1081 /// let mut wl = wlist![(1, "sup")];
1082 ///
1083 /// let item = WeightedItem::new(2, "sup");
1084 /// wl.merge_item(item);
1085 /// assert!(wl == wlist![(3, "sup")]);
1086 /// // "sup" merged with existing instance
1087 ///
1088 /// let item = WeightedItem::unit("elysion");
1089 /// wl.merge_item(item);
1090 /// assert!(wl == wlist![(3, "sup"), (1, "elysion")]);
1091 /// // "elysion" appended to end
1092 /// ```
1093 pub fn merge_item(&mut self, item: WeightedItem<V,W>) -> &mut Self
1094 {
1095 if let Some(existing) = self.data.iter_mut().find(|each| each.value == item.value) {
1096 existing.weight += item.weight;
1097 }
1098 else {
1099 self.data.push(item);
1100 }
1101
1102 self
1103 }
1104
1105 /// Merge a new item with `value` and `weight` into the list.
1106 ///
1107 /// See [`.merge_item()`](Self::merge_item) for details.
1108 pub fn merge_new_item(&mut self, weight: W, value: V) -> &mut Self
1109 {
1110 self.merge_item(WeightedItem { weight, value })
1111 }
1112
1113 /// Merge a new item with `value` and a weight of `1` into the list.
1114 ///
1115 /// See [`.merge_item()`](Self::merge_item) for details.
1116 pub fn merge_value(&mut self, value: V) -> &mut Self
1117 {
1118 self.merge_item(WeightedItem::unit(value))
1119 }
1120
1121 /// Merge the items of `other` into `self`, leaving `other` empty.
1122 ///
1123 /// See [`.merge_item()`](Self::merge_item) for details.
1124 pub fn merge_with(&mut self, other: WeightedList<V,W>) -> &mut Self
1125 {
1126 for item in other {
1127 self.merge_item(item);
1128 }
1129
1130 self
1131 }
1132
1133 /// Merge any items in the list with duplicate values by combining their weights with the first instance.
1134 ///
1135 /// # Usage
1136 ///
1137 /// ```
1138 /// # use weighted_list::*;
1139 /// let mut wl = wlist![
1140 /// (1, "sup"),
1141 /// (2, ""),
1142 /// (20, "sup")
1143 /// ];
1144 ///
1145 /// assert_eq!(
1146 /// *wl.merge_duplicates(),
1147 /// wlist![
1148 /// (21, "sup"),
1149 /// (2, ""),
1150 /// ]
1151 /// );
1152 /// ```
1153 pub fn merge_duplicates(&mut self) -> &mut Self
1154 {
1155 let orig = std::mem::replace(self, WeightedList::new());
1156 self.merge_with(orig);
1157 self
1158 }
1159}
1160
1161/// Methods for taking items from the list.
1162///
1163/// This involves returning values without removing the existing one, hence requiring `V: Clone`.
1164impl<V, W: Weight> WeightedList<V,W>
1165 where
1166 V: Clone
1167{
1168 /// Decrement the weight of the item at `weighted_index` by `1`. If its weight becomes non-positive as a result, remove the entire item. Returns a clone of the item with its updated weight.
1169 ///
1170 /// # Usage
1171 ///
1172 /// ```
1173 /// # use weighted_list::*;
1174 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1175 ///
1176 /// wl.take_one_at(2);
1177 /// assert_eq!( wl, wlist![(2, "sup"), (2, "nova"), (5, "shard")] );
1178 ///
1179 /// wl.take_one_at(2);
1180 /// assert_eq!( wl, wlist![(2, "sup"), (1, "nova"), (5, "shard")] );
1181 ///
1182 /// wl.take_one_at(2);
1183 /// assert_eq!( wl, wlist![(2, "sup"), (5, "shard")] );
1184 /// ```
1185 pub fn take_one_at(&mut self, weighted_index: W) -> WeightedItem<V,W>
1186 {
1187 self.take_by_at(W::one(), weighted_index)
1188 }
1189
1190 /// Decrement the weight of the item at `weighted_index` by `decrement`. If its weight becomes non-positive as a result, remove the entire item. Returns a clone of the item with its updated weight.
1191 ///
1192 /// # Panics
1193 ///
1194 /// Panics if `weighted_index` is out of bounds.
1195 ///
1196 /// # Usage
1197 ///
1198 /// ```
1199 /// # use weighted_list::*;
1200 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1201 ///
1202 /// wl.take_by_at(2, 2);
1203 /// assert_eq!( wl, wlist![(2, "sup"), (1, "nova"), (5, "shard")] );
1204 ///
1205 /// wl.take_by_at(2, 2);
1206 /// assert_eq!( wl, wlist![(2, "sup"), (5, "shard")]);
1207 /// ```
1208 pub fn take_by_at(&mut self, decrement: W, weighted_index: W) -> WeightedItem<V,W>
1209 {
1210 let idx = self._unweight_index_(weighted_index);
1211 let target = &mut self.data[idx];
1212
1213 if decrement >= target.weight {
1214 target.weight = W::zero();
1215 self.data.remove(idx)
1216 }
1217 else {
1218 target.weight -= decrement;
1219 target.clone()
1220 }
1221 }
1222
1223 /// Remove the entire item at `weighted_index`.
1224 ///
1225 /// # Usage
1226 ///
1227 /// ```
1228 /// # use weighted_list::*;
1229 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1230 ///
1231 /// wl.take_entire_at(3);
1232 /// assert_eq!( wl, wlist![(2, "sup"), (5, "shard")] );
1233 /// ```
1234 pub fn take_entire_at(&mut self, weighted_index: W) -> WeightedItem<V,W>
1235 {
1236 self.remove_at(weighted_index)
1237 }
1238}
1239
1240// == RANDOMISATION == //
1241/// Methods for out-of-place random sampling from a list.
1242impl<V, W: Weight> WeightedList<V,W>
1243{
1244 fn _get_random_weighted_index_up_to_<RNG>(&self, rng: &mut RNG, upper: W) -> Option<W>
1245 where RNG: Rng + ?Sized
1246 {
1247 let len: f64 = nums::cast::<W, f64>(upper)?;
1248 let scalar: f64 = rng.random();
1249
1250 let idx = (len * scalar).floor();
1251 let out = nums::cast::<f64, W>(idx)?;
1252
1253 Some(out)
1254 }
1255
1256 fn _get_random_weighted_index_<RNG>(&self, rng: &mut RNG) -> Option<W>
1257 where RNG: Rng + ?Sized
1258 {
1259 self._get_random_weighted_index_up_to_(rng, self.len())
1260 }
1261
1262 /// Select a random item from the list and return its value, using weighted randomisation.
1263 ///
1264 /// # Usage
1265 ///
1266 /// ```
1267 /// # use weighted_list::*;
1268 /// let wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1269 ///
1270 /// wl.select_random_value(&mut rand::rng());
1271 /// // could give:
1272 /// // - Some("sup" ) with 20% probability
1273 /// // - Some("nova" ) with 30% probability
1274 /// // - Some("shard") with 50% probability
1275 /// ```
1276 pub fn select_random_value<RNG>(&self, rng: &mut RNG) -> Option<&V>
1277 where RNG: Rng + ?Sized
1278 {
1279 let out = &self.select_random_item(rng)?.value;
1280 Some(out)
1281 }
1282
1283 /// Select a random item from the list, using weighted randomisation.
1284 ///
1285 /// # Usage
1286 ///
1287 /// ```
1288 /// # use weighted_list::*;
1289 /// let wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1290 ///
1291 /// wl.select_random_item(&mut rand::rng());
1292 /// // could give:
1293 /// // - Some(WeightedItem { 2, "sup" }) with 20% probability
1294 /// // - Some(WeightedItem { 3, "nova" }) with 30% probability
1295 /// // - Some(WeightedItem { 5, "shard" }) with 50% probability
1296 /// ```
1297 pub fn select_random_item<RNG>(&self, rng: &mut RNG) -> Option<&WeightedItem<V,W>>
1298 where RNG: Rng + ?Sized
1299 {
1300 if self.data.is_empty() { return None }
1301
1302 let idx = self._get_random_weighted_index_(rng)?;
1303 let out = &self[idx];
1304
1305 Some(out)
1306 }
1307}
1308
1309/// Methods for in-place random sampling from a list, decreasing weights of items that are chosen.
1310impl<V, W: Weight> WeightedList<V,W>
1311 where
1312 V: Clone
1313{
1314 /// Select a random item from the list using weighted randomisation, and decrement its weight by `1`.
1315 ///
1316 /// # Usage
1317 ///
1318 /// ```
1319 /// # use weighted_list::*;
1320 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1321 ///
1322 /// wl.take_one_random(&mut rand::rng());
1323 /// // could give:
1324 /// // - Some(WeightedItem { 1, "sup" }) with 20% probability
1325 /// // - Some(WeightedItem { 2, "nova" }) with 30% probability
1326 /// // - Some(WeightedItem { 4, "shard" }) with 50% probability
1327 /// ```
1328 pub fn take_one_random<RNG>(&mut self, rng: &mut RNG) -> Option<WeightedItem<V,W>>
1329 where RNG: Rng + ?Sized
1330 {
1331 self.take_by_random(rng, W::one())
1332 }
1333
1334 /// Select a random item from the list using weighted randomisation, and decrement its weight by `decrement`.
1335 ///
1336 /// # Usage
1337 ///
1338 /// ```
1339 /// # use weighted_list::*;
1340 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1341 ///
1342 /// wl.take_by_random(&mut rand::rng(), 2);
1343 /// // could give:
1344 /// // - Some(WeightedItem { 0, "sup" }) with 20% probability
1345 /// // - Some(WeightedItem { 1, "nova" }) with 30% probability
1346 /// // - Some(WeightedItem { 3, "shard" }) with 50% probability
1347 /// ```
1348 pub fn take_by_random<RNG>(&mut self, rng: &mut RNG, decrement: W) -> Option<WeightedItem<V,W>>
1349 where RNG: Rng + ?Sized
1350 {
1351 if self.data.is_empty() { return None }
1352
1353 let idx = self._get_random_weighted_index_(rng)?;
1354 let out = self.take_by_at(decrement, idx);
1355
1356 Some(out)
1357 }
1358
1359 /// Select and remove a random item from the list, using weighted randomisation.
1360 ///
1361 /// # Usage
1362 ///
1363 /// ```
1364 /// # use weighted_list::*;
1365 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1366 ///
1367 /// wl.take_entire_random(&mut rand::rng());
1368 /// // could give:
1369 /// // - Some(WeightedItem { 2, "sup" }) with 20% probability
1370 /// // - Some(WeightedItem { 3, "nova" }) with 30% probability
1371 /// // - Some(WeightedItem { 5, "shard" }) with 50% probability
1372 ///
1373 /// assert!( wl.total_items() == 2 );
1374 /// ```
1375 pub fn take_entire_random<RNG>(&mut self, rng: &mut RNG) -> Option<WeightedItem<V,W>>
1376 where RNG: Rng + ?Sized
1377 {
1378 if self.data.is_empty() { return None }
1379
1380 let idx = self._get_random_weighted_index_(rng)?;
1381 let out = self.take_entire_at(idx);
1382
1383 Some(out)
1384 }
1385}
1386
1387/// Random sampling methods which use the bon builder syntax.
1388#[bon]
1389impl<V, W: Weight> WeightedList<V,W>
1390 where
1391 V: Clone + Eq
1392{
1393 /// Select `count` values using weighted randomisation.
1394 ///
1395 /// Call this method using `bon` builder syntax (see § Usage below).
1396 ///
1397 /// # Options
1398 ///
1399 /// ```text
1400 /// rng: RNG,
1401 /// count: usize,
1402 /// replace: bool = true,
1403 /// decrement: W = 1,
1404 /// ```
1405 ///
1406 /// - `count`: How many values to select.
1407 /// - `replace` (optional): If `true`, items do not have their weight decremented after selection, and infinite values can be selected. If `false`, items have their weight decremented after selection – this would mean at most [`self.len()`](Self::len) values are returned.
1408 /// - `decrement` (optional): How much to decrement weights by if `replace` is `false`.
1409 ///
1410 /// # Usage
1411 ///
1412 /// This method uses the bon builder syntax:
1413 ///
1414 /// ```
1415 /// # use weighted_list::*;
1416 /// let mut pool = wlist![
1417 /// (2, "sup".to_string()),
1418 /// (3, "nova".to_string()),
1419 /// (5, "shard".to_string()),
1420 /// ];
1421 ///
1422 /// let mut rng = rand::rng();
1423 ///
1424 /// // with replacement
1425 /// let selected =
1426 /// pool.select_random_values()
1427 /// .rng(&mut rng)
1428 /// .count(3)
1429 /// .call();
1430 ///
1431 /// assert!(selected.len() == 3);
1432 ///
1433 /// // without replacement
1434 /// let selected =
1435 /// pool.select_random_values()
1436 /// .rng(&mut rng)
1437 /// .count(10)
1438 /// .replace(false)
1439 /// .decrement(2)
1440 /// .call();
1441 ///
1442 /// assert!(selected.len() == 6);
1443 /// ```
1444 ///
1445 /// # Notes
1446 ///
1447 /// - It is not guaranteed that the results will have exactly `count` values.
1448 /// - If `count` exceeds the maximum possible number of values that can be returned, excess iterations will be skipped.
1449 /// - If selection for an iteration fails, that value is excluded from the output list.
1450 /// - This method reserves a `Vec<>` with capacity `count` initially, so be careful of passing in extremely large `count`s.
1451 #[builder]
1452 pub fn select_random_values<RNG>(&self,
1453 rng: &mut RNG,
1454 count: usize,
1455 replace: Option<bool>,
1456 decrement: Option<W>,
1457 ) -> Vec<V>
1458 where RNG: Rng + ?Sized
1459 {
1460 let replace = replace.unwrap_or(true);
1461 let decrement = decrement.unwrap_or(W::one());
1462
1463 if replace {
1464 (0..count)
1465 .filter_map(|_| self.select_random_value(rng).cloned())
1466 .collect()
1467 }
1468 else {
1469 let mut pool = self.clone();
1470
1471 let mut out = Vec::with_capacity(
1472 if count > 16 {
1473 count.min(self.total_items())
1474 } else {
1475 count
1476 }
1477 );
1478
1479 for _ in 0..count {
1480 if self.is_zero() { break }
1481
1482 if let Some(item) = pool.take_by_random(rng, decrement) {
1483 out.push(item.value);
1484 }
1485 }
1486
1487 out
1488 }
1489 }
1490
1491 /// Select `count` unique values using weighted randomisation.
1492 ///
1493 /// Call this method using `bon` builder syntax (see [§ Usage](Self::select_random_values_unique#usage) below).
1494 ///
1495 /// # Options
1496 ///
1497 /// ```text
1498 /// rng: RNG,
1499 /// count: usize,
1500 /// merge_duplicates: bool = false,
1501 /// ```
1502 ///
1503 /// - `count`: How many values to select.
1504 /// - `merge_duplicates`: Whether to treat duplicate values as non-unique (see [§ Notes](Self::select_random_values_unique#notes) below for details).
1505 ///
1506 /// # Usage
1507 ///
1508 /// This method uses the bon builder syntax:
1509 ///
1510 /// ```
1511 /// # use weighted_list::*;
1512 /// let pool = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1513 ///
1514 /// let selected =
1515 /// pool.select_random_values_unique()
1516 /// .rng(&mut rand::rng())
1517 /// .count(3)
1518 /// .call();
1519 /// ```
1520 ///
1521 /// # Notes
1522 ///
1523 /// By default, this treats each *individual item* as ‘unique’. For instance, the following lists all contain 3 unique values:
1524 ///
1525 /// ```rust
1526 /// # use weighted_list::*;
1527 /// wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1528 /// wlist![(2, "sup"), (3, "sup"), (5, "shard")];
1529 /// wlist![(2, "sup"), (3, "sup"), (5, "sup") ];
1530 /// ```
1531 ///
1532 /// This means using `select_random_values_unique()` on them will return at most [`self.total_items()`](Self::total_items) values.
1533 ///
1534 /// However, if `merge_duplicates` is `true`, then duplicate values (using `Eq` comparison) will be treated as non-unique. In this case, the previous lists have 3, 2, and 1 unique values, respectively.
1535 ///
1536 /// ```
1537 /// # use weighted_list::*;
1538 /// let mut pool = wlist![
1539 /// (2, "sup"),
1540 /// (2, "sup"),
1541 /// (5, "shard"),
1542 /// ];
1543 ///
1544 /// let mut rng = rand::rng();
1545 ///
1546 /// // non-merged
1547 /// let selected = pool
1548 /// .select_random_values_unique()
1549 /// .rng(&mut rng)
1550 /// .count(3)
1551 /// .call();
1552 ///
1553 /// /* guaranteed to contain {"sup", "sup", "shard"} in some order */
1554 /// assert!(selected.len() == 3);
1555 ///
1556 /// // merged
1557 /// let selected = pool
1558 /// .select_random_values_unique()
1559 /// .rng(&mut rng)
1560 /// .count(3)
1561 /// .merge_duplicates(true)
1562 /// .call();
1563 ///
1564 /// /* guaranteed to contain {"sup", "shard"} in some order */
1565 /// assert!(selected.len() == 2);
1566 /// ```
1567 #[builder]
1568 pub fn select_random_values_unique<RNG>(&self,
1569 rng: &mut RNG,
1570 count: usize,
1571 merge_duplicates: Option<bool>,
1572 ) -> Vec<V>
1573 where
1574 RNG: Rng + ?Sized,
1575 V: Clone + Eq,
1576 {
1577 let merge_duplicates = merge_duplicates.unwrap_or(false);
1578
1579 let mut l = self.len();
1580 let mut seen_indices = HashSet::<usize>::new();
1581
1582 let mut out = Vec::with_capacity(
1583 if count > 16 {
1584 count.min(self.total_items())
1585 } else {
1586 count
1587 }
1588 );
1589
1590 for _ in 0..count
1591 {
1592 if l == W::zero() { break }
1593
1594 let Some(weighted_index) = self._get_random_weighted_index_up_to_(rng, l) else { continue };
1595 let Some(idx) = self._unweight_index_skipping_(weighted_index, &seen_indices) else { continue };
1596 let target = &self.data[idx];
1597
1598 if !merge_duplicates {
1599 seen_indices.insert(idx);
1600 l -= target.weight;
1601 }
1602 else {
1603 /* Yeah, the repeated traversal is horrific, but I can’t see any other way... we’ve got to discount *all* duplicates of the value we pick */
1604 let (indices, weights): (Vec<usize>, Vec<W>) =
1605 self.data.iter().enumerate()
1606 .filter_map(
1607 |(i, item)| (item.value == target.value).then_some((i, item.weight))
1608 )
1609 .unzip();
1610
1611 seen_indices.extend(indices);
1612 l -= weights.into_iter().sum::<W>();
1613 }
1614
1615 out.push(target.value.clone());
1616 }
1617
1618 out
1619 }
1620
1621 /// Take `count` values using weighted randomisation.
1622 #[builder]
1623 pub fn take_random_values<RNG>(&mut self,
1624 rng: &mut RNG,
1625 count: usize,
1626 take_entire: Option<bool>,
1627 decrement: Option<W>,
1628 ) -> Vec<V>
1629 where RNG: Rng + ?Sized
1630 {
1631 let take_entire = take_entire.unwrap_or(true);
1632 let decrement = decrement.unwrap_or(W::one());
1633
1634 let mut n = 0;
1635 let mut out = Vec::with_capacity(count);
1636
1637 loop
1638 {
1639 n += 1;
1640 if n > count || self.data.is_empty() { break }
1641
1642 if let Some(item) = {
1643 if take_entire { self.take_entire_random(rng) }
1644 else { self.take_by_random(rng, decrement) }
1645 } {
1646 out.push(item.value.clone());
1647 }
1648 }
1649
1650 out
1651 }
1652
1653 /// Take `count` unique values using weighted randomisation.
1654 ///
1655 /// This method is separate to `.take_random_values` due to having more restrictive trait bounds on `V` and using a different algorithm with heavier performance.
1656 #[builder]
1657 pub fn take_random_values_unique<RNG>(&mut self,
1658 rng: &mut RNG,
1659 count: usize,
1660 decrement: Option<W>,
1661 ) -> Vec<V>
1662 where RNG: Rng + ?Sized,
1663 {
1664 let decrement = decrement.unwrap_or(W::one());
1665
1666 let mut l = self.len();
1667 let mut seen_indices = HashSet::<usize>::new();
1668
1669 let mut out = Vec::with_capacity(
1670 /* NOTE: To guard against erroneous overallocations */
1671 if count > 16 {
1672 count.min(self.total_items())
1673 } else {
1674 count
1675 }
1676 );
1677
1678 for _ in 0..count
1679 {
1680 if l == W::zero() || self.is_zero() { break }
1681
1682 let Some(weighted_index) = self._get_random_weighted_index_up_to_(rng, l) else { continue };
1683 let Some(idx) = self._unweight_index_skipping_(weighted_index, &seen_indices) else { continue };
1684 let target = &self.data[idx];
1685
1686 /* Yeah, the repeated traversal is horrific, but I can’t see any other way... we’ve got to discount *all* duplicates of the value we pick */
1687 let (indices, weights): (Vec<usize>, Vec<W>) =
1688 self.data.iter().enumerate()
1689 .filter_map(
1690 |(i, item)| (item.value == target.value).then_some((i, item.weight))
1691 )
1692 .unzip();
1693
1694 seen_indices.extend(indices);
1695 l -= weights.into_iter().sum::<W>();
1696
1697 /* NOTE: Upgrade to mutable reference here to avoid conflict with earlier */
1698 let target = &mut self.data[idx];
1699
1700 if decrement >= target.weight {
1701 target.weight = W::zero();
1702 } else {
1703 target.weight -= decrement;
1704 }
1705
1706 out.push(target.value.clone());
1707 }
1708
1709 self.prune();
1710
1711 out
1712 }
1713}
1714
1715/// Methods for shuffling data.
1716impl<V, W: Weight> WeightedList<V,W>
1717 where
1718 V: Clone,
1719{
1720 /// Shuffle the order of items in the list (in-place).
1721 pub fn shuffle_items<RNG>(&mut self, rng: &mut RNG) -> &mut Self
1722 where RNG: Rng + ?Sized
1723 {
1724 self.data.shuffle(rng);
1725 self
1726 }
1727
1728 /// Return a clone with the order of items shuffled.
1729 ///
1730 /// Out-of-place version of [`.shuffle_items()`](Self::shuffle_items).
1731 pub fn shuffled_items<RNG>(&self, rng: &mut RNG) -> Self
1732 where RNG: Rng + ?Sized
1733 {
1734 let mut out = self.clone();
1735 out.shuffle_items(rng);
1736
1737 out
1738 }
1739
1740 /// Shuffle the pairings of (weight, value) for items in the list (in-place).
1741 ///
1742 /// Values remain in the same order, while weights are re-assigned.
1743 ///
1744 /// # Usage
1745 ///
1746 /// ```
1747 /// # use weighted_list::*;
1748 /// let mut wl = wlist![(2, "sup"), (3, "nova"), (5, "shard")];
1749 ///
1750 /// wl.shuffle_weights(&mut rand::rng());
1751 ///
1752 /// println!("{wl}");
1753 /// // could give:
1754 /// // WeightedList[{3, sup}, {5, nova}, {2, shard}]
1755 ///
1756 /// println!("{wl}");
1757 /// // could now give:
1758 /// // WeightedList[{2, sup}, {5, nova}, {3, shard}]
1759 /// ```
1760 pub fn shuffle_weights<RNG>(&mut self, rng: &mut RNG) -> &mut Self
1761 where RNG: Rng + ?Sized
1762 {
1763 let mut weights: Vec<W> = self.weights().collect();
1764 weights.shuffle(rng);
1765
1766 for item in &mut self.data {
1767 /* guaranteed to be Some */
1768 item.weight = weights.pop().unwrap();
1769 }
1770
1771 self
1772 }
1773
1774 /// Return a clone of the list with (weight, value) pairings shuffled.
1775 ///
1776 /// Out-of-place version of [`.shuffle_weights()`](Self::shuffle_weights).
1777 pub fn shuffled_weights<RNG>(&self, rng: &mut RNG) -> Self
1778 where RNG: Rng + ?Sized
1779 {
1780 let mut out = self.clone();
1781 out.shuffle_weights(rng);
1782
1783 out
1784 }
1785}
1786
1787// == INTERNAL == //
1788impl<V, W: Weight> WeightedList<V,W>
1789{
1790 /// Convert a `weighted_index` to its unweighted equivalent in the underlying `Vec<>`. Does not panic on overflow and instead returns `Vec::len()`.
1791 fn _unweight_index_nopanic_(&self, weighted_index: W) -> usize
1792 {
1793 let mut t = W::zero();
1794 let mut i = 0;
1795
1796 for item in &self.data {
1797 t += item.weight;
1798
1799 if t > weighted_index {
1800 return i;
1801 }
1802
1803 i += 1;
1804 }
1805
1806 i
1807 }
1808
1809 /// Convert a `weighted_index` to its unweighted equivalent in the underlying `Vec<>`. Panics on overflow.
1810 fn _unweight_index_(&self, weighted_index: W) -> usize
1811 {
1812 let mut t = W::zero();
1813
1814 for (i, item) in self.data.iter().enumerate() {
1815 t += item.weight;
1816
1817 if t > weighted_index {
1818 return i;
1819 }
1820 }
1821
1822 panic!(
1823 "index out of bounds: the len is {:?} but the index is {:?}",
1824 self.len(), weighted_index
1825 );
1826 }
1827
1828 /// Variant of `._unweighted_index_()` for random selection enforcing unique outputs.
1829 fn _unweight_index_skipping_(&self,
1830 weighted_index: W,
1831 seen: &HashSet<usize>,
1832 ) -> Option<usize>
1833 {
1834 let mut t = W::zero();
1835
1836 for (i, item) in self.data.iter().enumerate()
1837 {
1838 if seen.contains(&i) { continue }
1839
1840 t += item.weight;
1841
1842 if t > weighted_index {
1843 return Some(i);
1844 }
1845 }
1846
1847 None
1848 }
1849}
1850
1851
1852#[cfg(test)] mod tests
1853{
1854 use super::*;
1855
1856 fn el() -> WeightedList<String, i32>
1857 {
1858 wlist![]
1859 }
1860
1861 fn wl() -> WeightedList<String, i32>
1862 {
1863 wlist![
1864 (2, "sup".to_string()),
1865 (3, "nova".to_string()),
1866 (5, "shard".to_string()),
1867 ]
1868 }
1869
1870 #[test] fn _unweight_index_()
1871 {
1872 let list = wl();
1873 assert_eq!( list._unweight_index_(0), 0 );
1874 assert_eq!( list._unweight_index_(1), 0 );
1875 assert_eq!( list._unweight_index_(2), 1 );
1876 assert_eq!( list._unweight_index_(3), 1 );
1877 assert_eq!( list._unweight_index_(4), 1 );
1878 assert_eq!( list._unweight_index_(5), 2 );
1879 assert_eq!( list._unweight_index_(6), 2 );
1880 assert_eq!( list._unweight_index_(7), 2 );
1881 assert_eq!( list._unweight_index_(8), 2 );
1882 assert_eq!( list._unweight_index_(9), 2 );
1883 }
1884
1885 #[test] #[should_panic] fn _unweight_index_empty_()
1886 {
1887 el()._unweight_index_(0);
1888 }
1889
1890 #[test] #[should_panic] fn _unweight_index_out_of_bounds_()
1891 {
1892 wl()._unweight_index_(10);
1893 }
1894
1895 #[test] fn _unweight_index_nopanic_()
1896 {
1897 let list = wl();
1898 assert_eq!( list._unweight_index_nopanic_(10), 3 );
1899 assert_eq!( list._unweight_index_nopanic_(11), 3 );
1900 assert_eq!( list._unweight_index_nopanic_(12), 3 );
1901 }
1902
1903 #[test] fn _unweight_index_skipping_()
1904 {
1905 let list = wl();
1906
1907 let seen = HashSet::from([0]);
1908 assert_eq!( list._unweight_index_skipping_(0, &seen), Some(1) );
1909 assert_eq!( list._unweight_index_skipping_(1, &seen), Some(1) );
1910 assert_eq!( list._unweight_index_skipping_(2, &seen), Some(1) );
1911 assert_eq!( list._unweight_index_skipping_(3, &seen), Some(2) );
1912 assert_eq!( list._unweight_index_skipping_(4, &seen), Some(2) );
1913 assert_eq!( list._unweight_index_skipping_(5, &seen), Some(2) );
1914 assert_eq!( list._unweight_index_skipping_(6, &seen), Some(2) );
1915 assert_eq!( list._unweight_index_skipping_(7, &seen), Some(2) );
1916
1917 let seen = HashSet::from([1]);
1918 assert_eq!( list._unweight_index_skipping_(0, &seen), Some(0) );
1919 assert_eq!( list._unweight_index_skipping_(1, &seen), Some(0) );
1920 assert_eq!( list._unweight_index_skipping_(2, &seen), Some(2) );
1921 assert_eq!( list._unweight_index_skipping_(3, &seen), Some(2) );
1922 assert_eq!( list._unweight_index_skipping_(4, &seen), Some(2) );
1923 assert_eq!( list._unweight_index_skipping_(5, &seen), Some(2) );
1924 assert_eq!( list._unweight_index_skipping_(6, &seen), Some(2) );
1925 }
1926}