From b2eb86a77c3d2743ec7eeb91a26ef78995d0d480 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Thu, 26 Dec 2024 01:18:59 +0100 Subject: [PATCH] add `shuffle(::NTuple)` to `Random` Fixes #56728 --- stdlib/Random/src/misc.jl | 30 ++++++++++++++++++++++++++++++ stdlib/Random/test/runtests.jl | 28 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/stdlib/Random/src/misc.jl b/stdlib/Random/src/misc.jl index 908776383d45f..2b5468a172052 100644 --- a/stdlib/Random/src/misc.jl +++ b/stdlib/Random/src/misc.jl @@ -183,6 +183,36 @@ ltm52(n::Int, mask::Int=nextpow(2, n)-1) = LessThan(n-1, Masked(mask, UInt52Raw( ## shuffle & shuffle! +function _tuple_without_element_at_index(tup::(Tuple{T, Vararg{T, N}} where {T}), i::Int) where {N} + clo = let tup = tup, i = i + function closure(j::Int) + k = if j < i + j + else + j + 1 + end + tup[k] + end + end + ntuple(clo, Val{N}()) +end + +function shuffle(rng::AbstractRNG, tup::(Tuple{Vararg{T, N}} where {T})) where {N} + if tup isa Tuple{Any, Vararg} + let i = rand(rng, Base.OneTo(N)) # Fisher–Yates shuffle + s = _tuple_without_element_at_index(tup, i) + t = shuffle(rng, s) + (tup[i], t...) + end + else + tup + end::typeof(tup) +end + +function shuffle(tup::NTuple) + shuffle(default_rng(), tup) +end + """ shuffle!([rng=default_rng(),] v::AbstractArray) diff --git a/stdlib/Random/test/runtests.jl b/stdlib/Random/test/runtests.jl index 9b46951f63ff5..74cb1292f4653 100644 --- a/stdlib/Random/test/runtests.jl +++ b/stdlib/Random/test/runtests.jl @@ -1032,6 +1032,34 @@ end @test maximum(m) <= 0.106 end +@testset "`shuffle(::NTuple)`" begin + @testset "sorted" begin + for n ∈ 0:20 + tup = ntuple(identity, n) + @test tup === sort(@inferred shuffle(tup)) + end + end + @testset "not identity" begin + function is_not_identity_at_least_once() + function f(::Any) + tup = ntuple(identity, 9) + tup !== shuffle(tup) + end + @test any(f, 1:1000000) + end + is_not_identity_at_least_once() + end + @testset "no heap allocation" begin + function no_heap_allocation(tup) + @test iszero(@allocated shuffle(tup)) + end + for n ∈ 0:9 + tup = ntuple(identity, n) + no_heap_allocation(tup) + end + end +end + # issue #42752 # test that running finalizers that launch tasks doesn't change RNG stream function f42752(do_gc::Bool, cell = (()->Any[[]])())