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