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