]> git.nega.tv - josh/narcissus/commitdiff
Add `select` and `shuffle` functions to `Pcg64`
authorJoshua Simmons <josh@nega.tv>
Wed, 1 Mar 2023 19:25:09 +0000 (20:25 +0100)
committerJoshua Simmons <josh@nega.tv>
Wed, 1 Mar 2023 19:25:09 +0000 (20:25 +0100)
bins/narcissus/src/main.rs
libs/narcissus-core/src/rand.rs

index ce00977a41a9e4e1ab06d4f16345f9d4560b8103..7485aefc185ded09640d643f3caa225cac0361ef 100644 (file)
@@ -299,14 +299,12 @@ pub fn main() {
                     x += font.kerning_advance(prev_glyph_index, glyph_index) * scale;
                 }
 
-                const COLOR_SERIES: [u32; 4] = [0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63];
-                let color = COLOR_SERIES[rng.next_bound_u64(4) as usize];
-
+                const COLOR_SERIES: &[u32; 4] = &[0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63];
                 glyph_instances.push(GlyphInstance {
                     x,
                     y,
                     touched_glyph_index,
-                    color,
+                    color: *rng.select(COLOR_SERIES).unwrap(),
                 });
 
                 x += advance_width * scale;
index ac64dfb69414ede7a4065fdcd72dd34b5fb0095e..9c2af1d830c456d2616e0c440b49d7ecfe831a62 100644 (file)
@@ -50,6 +50,32 @@ impl Pcg64 {
         let value = (self.next_u64() >> (64 - 25)) as i64 as f32;
         value * 5.960_464_5e-8 // 0x1p-24f
     }
+
+    /// Randomly select an an element from `slice` with uniform probability.
+    pub fn select<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> {
+        if slice.is_empty() {
+            None
+        } else {
+            let index = self.next_bound_u64(slice.len() as u64) as usize;
+            slice.get(index)
+        }
+    }
+
+    /// Shuffle the elements in `slice` in-place.
+    ///
+    /// Note that as `Pcg64` is initialized with a 128 bit seed, it's only possible to generate
+    /// `2^128` permutations. This means for slices larger than 34 elements, this function can no
+    /// longer produce all permutations.
+    pub fn shuffle<T>(&mut self, slice: &mut [T]) {
+        if !slice.is_empty() {
+            let mut i = slice.len() - 1;
+            while i >= 1 {
+                let j = self.next_bound_u64((i + 1) as u64) as usize;
+                slice.swap(i, j);
+                i -= 1;
+            }
+        }
+    }
 }
 
 impl Default for Pcg64 {
@@ -60,6 +86,8 @@ impl Default for Pcg64 {
 
 #[cfg(test)]
 mod tests {
+    use std::collections::HashSet;
+
     use super::*;
 
     #[test]
@@ -100,4 +128,40 @@ mod tests {
         assert_eq!(rng.next_bound_u64(2), 1);
         assert_eq!(rng.next_bound_u64(2), 0);
     }
+
+    #[test]
+    fn shuffle_generates_all_permutations() {
+        let mut array: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
+        let mut permutations = HashSet::new();
+        let mut rng = Pcg64::new();
+        // 8P8 = 40_320 = number of possible permutations of 8 elements.
+        while permutations.len() != 40_320 {
+            rng.shuffle(&mut array);
+            permutations.insert(u64::from_le_bytes(array));
+        }
+    }
+
+    #[test]
+    fn shuffle_empty_slice() {
+        let slice: &mut [u8] = &mut [];
+        let mut rng = Pcg64::new();
+        rng.shuffle(slice)
+    }
+
+    #[test]
+    fn select_visits_all_elements() {
+        let array = &[0, 1, 2, 3, 4, 5, 6, 7];
+        let mut selected = HashSet::<u8>::from_iter(array.iter().copied());
+        let mut rng = Pcg64::new();
+        while !selected.is_empty() {
+            selected.remove(rng.select(array).unwrap());
+        }
+    }
+
+    #[test]
+    fn select_empty_slice() {
+        let slice: &mut [u8] = &mut [];
+        let mut rng = Pcg64::new();
+        assert_eq!(rng.select(slice), None);
+    }
 }