weighted_list/
weighted_list.rs

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