Commit 8828fe3a7d

InKryption <inkryption07@gmail.com>
2022-11-02 18:23:14
rand: add shuffleWithIndex
and reimplement shuffle in terms of it. This allows the caller to specify an index type of a fixed bit width, allowing results to be independent usize.
1 parent b40fc70
Changed files (1)
lib
lib/std/rand.zig
@@ -323,14 +323,38 @@ pub const Random = struct {
     }
 
     /// Shuffle a slice into a random order.
-    pub fn shuffle(r: Random, comptime T: type, buf: []T) void {
+    ///
+    /// Note that this will not yield consistent results across all targets
+    /// due to dependence on the representation of `usize` as an index.
+    /// See `shuffleWithIndex` for further commentary.
+    pub inline fn shuffle(r: Random, comptime T: type, buf: []T) void {
+        r.shuffleWithIndex(T, buf, usize);
+    }
+
+    /// Shuffle a slice into a random order, using an index of a
+    /// specified type to maintain distribution across targets.
+    /// Asserts the index type can represent `buf.len`.
+    ///
+    /// Indexes into the slice are generated using the specified `Index`
+    /// type, which determines distribution properties. This allows for
+    /// results to be independent of `usize` representation.
+    ///
+    /// Prefer `shuffle` if this isn't important.
+    ///
+    /// See `intRangeLessThan`, which this function uses,
+    /// for commentary on the runtime of this function.
+    pub fn shuffleWithIndex(r: Random, comptime T: type, buf: []T, comptime Index: type) void {
+        comptime std.debug.assert(@typeInfo(Index).Int.signedness == .unsigned);
+        const MinInt = std.meta.Int(.unsigned, @min(@typeInfo(Index).Int.bits, @typeInfo(usize).Int.bits));
         if (buf.len < 2) {
             return;
         }
 
-        var i: usize = 0;
-        while (i < buf.len - 1) : (i += 1) {
-            const j = r.intRangeLessThan(usize, i, buf.len);
+        // `i <= j < max <= maxInt(MinInt)`
+        const max = @intCast(MinInt, buf.len);
+        var i: MinInt = 0;
+        while (i < max - 1) : (i += 1) {
+            const j = @intCast(MinInt, r.intRangeLessThan(Index, i, max));
             mem.swap(T, &buf[i], &buf[j]);
         }
     }