From 4d02c437e7f634655419dfdb65933b93c370da38 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:36:25 +0100 Subject: [PATCH] add `shuffle` implementation to preserve stability (#17) --- .github/workflows/CI.yml | 8 ++++++++ Project.toml | 2 +- src/StableRNGs.jl | 22 ++++++++++++++++++++++ test/runtests.jl | 8 ++------ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d1117ab..6854773 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -20,6 +20,14 @@ jobs: matrix: version: - "1.0" # earliest supported release + - "1.1" + - "1.2" + - "1.3" + - "1.4" + - "1.5" + - "1.6" + - "1.7" + - "1.8" - "1" # latest release - "nightly" os: diff --git a/Project.toml b/Project.toml index ae7252a..ae56068 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StableRNGs" uuid = "860ef19b-820b-49d6-a774-d7a799459cd3" authors = ["Rafael Fourquet "] -version = "1.0.0" +version = "1.0.1" [deps] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/src/StableRNGs.jl b/src/StableRNGs.jl index b9b0ba2..c661bc2 100644 --- a/src/StableRNGs.jl +++ b/src/StableRNGs.jl @@ -6,6 +6,13 @@ using Random: Random, AbstractRNG, Sampler, SamplerType import Random: rand, seed! +# Compat +if VERSION < v"1.2.0-" + using Base: has_offset_axes + require_one_based_indexing(A...) = !has_offset_axes(A...) || throw(ArgumentError("offset arrays are not supported but got an array with index other than 1")) +else + using Base: require_one_based_indexing +end # implementation of LehmerRNG based on the constants found at the # MIT licensed code by Melissa E. O'Neill at @@ -132,5 +139,20 @@ for T in Base.BitInteger_types SamplerRangeFast(r) end +# https://github.com/JuliaRandom/StableRNGs.jl/issues/10 +Random.shuffle(r::StableRNG, a::AbstractArray) = Random.shuffle!(r, Base.copymutable(a)) +function Random.shuffle!(r::StableRNG, a::AbstractArray) + require_one_based_indexing(a) + n = length(a) + n <= 1 && return a # nextpow below won't work with n == 0 + @assert n <= Int64(2)^52 + mask = nextpow(2, n) - 1 + for i = n:-1:2 + (mask >> 1) == i && (mask >>= 1) + j = 1 + rand(r, Random.ltm52(i, mask)) + a[i], a[j] = a[j], a[i] + end + return a +end end # module diff --git a/test/runtests.jl b/test/runtests.jl index d0384c9..e437e65 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -131,15 +131,11 @@ end end end +# Reference test @testset "`shuffle` stability" begin a = 1:10 a_shuffled = [4, 5, 6, 1, 3, 7, 10, 2, 8, 9] - - # If these tests fail due to changes in the algorithm used in `Random.shuffle`, - # we should vendor the old implementation here to keep it stable. - # See . - @test shuffle(StableRNG(10), a) == a_shuffled - + @test shuffle(StableRNG(10), a) == a_shuffled b = collect(a) shuffle!(StableRNG(10), b) @test b == a_shuffled