Skip to content

Commit f4af91d

Browse files
authored
add shuffle(::NTuple) to Random (#56906)
1 parent 179a9a1 commit f4af91d

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Standard library changes
7373

7474
* `randperm!` and `randcycle!` now support non-`Array` `AbstractArray` inputs, assuming they are mutable and their indices are one-based ([#58596]).
7575

76+
* `shuffle` now may take an argument of `NTuple` value ([#56906]).
77+
7678
#### REPL
7779

7880
* The display of `AbstractChar`s in the main REPL mode now includes LaTeX input information like what is shown in help mode ([#58181]).

stdlib/Random/src/misc.jl

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,34 @@ ltm52(n::Int, mask::Int=nextpow(2, n)-1) = LessThan(n-1, Masked(mask, UInt52Raw(
183183

184184
## shuffle & shuffle!
185185

186+
function shuffle(rng::AbstractRNG, tup::NTuple{N}) where {N}
187+
# `@inline` and `@inbounds` are here to help escape analysis eliminate the `Memory` allocation
188+
#
189+
# * `@inline` might be necessary because escape analysis relies on everything
190+
# touching the `Memory` being inlined because there's no interprocedural escape
191+
# analysis yet, relevant WIP PR: https://github.com/JuliaLang/julia/pull/56849
192+
#
193+
# * `@inbounds` might be necessary because escape analysis requires any throws of
194+
# `BoundsError` to be eliminated as dead code, because `BoundsError` stores the
195+
# array itself, making the throw escape the array from the function, relevant
196+
# WIP PR: https://github.com/JuliaLang/julia/pull/56167
197+
@inline let
198+
# use a narrow integer type to save stack space and prevent heap allocation
199+
Ind = if N typemax(UInt8)
200+
UInt8
201+
elseif N typemax(UInt16)
202+
UInt16
203+
else
204+
UInt
205+
end
206+
mem = @inbounds randperm!(rng, Memory{Ind}(undef, N))
207+
function closure(i::Int)
208+
@inbounds tup[mem[i]]
209+
end
210+
ntuple(closure, Val{N}())
211+
end
212+
end
213+
186214
"""
187215
shuffle!([rng=default_rng(),] v::AbstractArray)
188216
@@ -238,13 +266,16 @@ end
238266
shuffle!(a::AbstractArray) = shuffle!(default_rng(), a)
239267

240268
"""
241-
shuffle([rng=default_rng(),] v::AbstractArray)
269+
shuffle([rng=default_rng(),] v::Union{NTuple,AbstractArray})
242270
243271
Return a randomly permuted copy of `v`. The optional `rng` argument specifies a random
244272
number generator (see [Random Numbers](@ref)).
245273
To permute `v` in-place, see [`shuffle!`](@ref). To obtain randomly permuted
246274
indices, see [`randperm`](@ref).
247275
276+
!!! compat "Julia 1.13"
277+
Shuffling an `NTuple` value requires Julia v1.13 or above.
278+
248279
# Examples
249280
```jldoctest
250281
julia> shuffle(Xoshiro(123), Vector(1:10))
@@ -261,8 +292,10 @@ julia> shuffle(Xoshiro(123), Vector(1:10))
261292
7
262293
```
263294
"""
295+
function shuffle end
296+
264297
shuffle(r::AbstractRNG, a::AbstractArray) = shuffle!(r, copymutable(a))
265-
shuffle(a::AbstractArray) = shuffle(default_rng(), a)
298+
shuffle(a::Union{NTuple, AbstractArray}) = shuffle(default_rng(), a)
266299

267300
shuffle(r::AbstractRNG, a::Base.OneTo) = randperm(r, last(a))
268301

stdlib/Random/test/runtests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,23 @@ end
10711071
@test maximum(m) <= 0.106
10721072
end
10731073

1074+
@testset "`shuffle(::NTuple)`" begin
1075+
@testset "sorted" begin
1076+
for n 0:20
1077+
tup = ntuple(identity, n)
1078+
@test tup === sort(@inferred shuffle(tup))
1079+
end
1080+
end
1081+
@testset "not identity" begin
1082+
function shuffle_is_identity()
1083+
tup = ntuple(identity, 9)
1084+
tup === shuffle(tup)
1085+
end
1086+
# shuffling may behave as the identity sometimes, but if it doesn't manage to actually reorder some of the elements at least once, something is wrong
1087+
@test any((_ -> !shuffle_is_identity()), 1:1000000)
1088+
end
1089+
end
1090+
10741091
# issue #42752
10751092
# test that running finalizers that launch tasks doesn't change RNG stream
10761093
function f42752(do_gc::Bool, cell = (()->Any[[]])())

0 commit comments

Comments
 (0)