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}