diff --git a/NEWS.md b/NEWS.md index 824d730ed3c3c..2aa8ccd216a68 100644 --- a/NEWS.md +++ b/NEWS.md @@ -50,6 +50,7 @@ Standard library changes #### Profile #### Random +* `rand` now supports sampling over `Tuple` types ([#50251]). #### REPL diff --git a/stdlib/Random/docs/src/index.md b/stdlib/Random/docs/src/index.md index e344e47947440..7ef885775c58f 100644 --- a/stdlib/Random/docs/src/index.md +++ b/stdlib/Random/docs/src/index.md @@ -81,7 +81,7 @@ Random.MersenneTwister Random.RandomDevice ``` -## Hooking into the `Random` API +## [Hooking into the `Random` API](@id rand-api-hook) There are two mostly orthogonal ways to extend `Random` functionalities: 1) generating random values of custom types diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index 8fa4cba78648b..a8b6ae0c41b03 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -311,12 +311,28 @@ include("XoshiroSimd.jl") Pick a random element or array of random elements from the set of values specified by `S`; `S` can be -* an indexable collection (for example `1:9` or `('x', "y", :z)`), -* an `AbstractDict` or `AbstractSet` object, +* an indexable collection (for example `1:9` or `('x', "y", :z)`) + +* an `AbstractDict` or `AbstractSet` object + * a string (considered as a collection of characters), or -* a type: the set of values to pick from is then equivalent to `typemin(S):typemax(S)` for - integers (this is not applicable to [`BigInt`](@ref)), to ``[0, 1)`` for floating - point numbers and to ``[0, 1)+i[0, 1)`` for complex floating point numbers; + +* a type from the list below, corresponding to the specified set of values + + + concrete integer types sample from `typemin(S):typemax(S)` (excepting [`BigInt`](@ref) which is not supported) + + + concrete floating point types sample from `[0, 1)` + + + concrete complex types `Complex{T}` if `T` is a sampleable type take their real and imaginary components + independently from the set of values corresponding to `T`, but are not supported if `T` is not sampleable. + + + all `<:AbstractChar` types sample from the set of valid Unicode scalars + + + a user-defined type and set of values; for implementation guidance please see [Hooking into the `Random` API](@ref rand-api-hook) + + + `S` can also be a tuple type of known size and where each parameter of `S` is itself a sampleable type; return a value of type `S`. + Note that tuple types such as `Tuple{Vararg{T}}` (unknown size) and `Tuple{1:2}` (parameterized with a value) are not supported. + `S` defaults to [`Float64`](@ref). When only one argument is passed besides the optional `rng` and is a `Tuple`, it is interpreted @@ -328,6 +344,9 @@ See also [`randn`](@ref) for normally distributed numbers, and [`rand!`](@ref) a !!! compat "Julia 1.1" Support for `S` as a tuple requires at least Julia 1.1. +!!! compat "Julia 1.11" + Support for `S` as a `Tuple` type requires at least Julia 1.11. + # Examples ```julia-repl julia> rand(Int, 2) diff --git a/stdlib/Random/src/generation.jl b/stdlib/Random/src/generation.jl index cc9840f678413..1e3edea21c03b 100644 --- a/stdlib/Random/src/generation.jl +++ b/stdlib/Random/src/generation.jl @@ -167,6 +167,20 @@ function rand(r::AbstractRNG, ::SamplerType{T}) where {T<:AbstractChar} (c < 0xd800) ? T(c) : T(c+0x800) end +### random tuples + +function Sampler(::Type{RNG}, ::Type{T}, n::Repetition) where {T<:Tuple, RNG<:AbstractRNG} + tail_sp_ = Sampler(RNG, Tuple{Base.tail(fieldtypes(T))...}, n) + SamplerTag{T}((Sampler(RNG, fieldtype(T, 1), n), tail_sp_.data...)) +end + +function Sampler(::Type{RNG}, ::Type{Tuple{Vararg{T, N}}}, n::Repetition) where {T, N, RNG<:AbstractRNG} + SamplerTag{Tuple{Vararg{T, N}}}((Sampler(RNG, T, n),)) +end + +function rand(rng::AbstractRNG, sp::SamplerTag{T}) where T<:Tuple + ntuple(i -> rand(rng, sp.data[min(i, length(sp.data))]), Val{fieldcount(T)}())::T +end ## Generate random integer within a range diff --git a/stdlib/Random/test/runtests.jl b/stdlib/Random/test/runtests.jl index 7aae37d42a23b..3c760441d3746 100644 --- a/stdlib/Random/test/runtests.jl +++ b/stdlib/Random/test/runtests.jl @@ -285,9 +285,13 @@ end for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) ftypes = [Float16, Float32, Float64, FakeFloat64, BigFloat] cftypes = [ComplexF16, ComplexF32, ComplexF64, ftypes...] - types = [Bool, Char, BigFloat, Base.BitInteger_types..., cftypes...] + types = [Bool, Char, BigFloat, Tuple{Bool, Tuple{Int, Char}}, Base.BitInteger_types..., cftypes...] randset = Set(rand(Int, 20)) randdict = Dict(zip(rand(Int,10), rand(Int, 10))) + + randwidetup = Tuple{Bool, Char, Vararg{Tuple{Int, Float64}, 14}} + @inferred rand(rng..., randwidetup) + collections = [BitSet(rand(1:100, 20)) => Int, randset => Int, GenericSet(randset) => Int, @@ -319,7 +323,9 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) a2 = f(rng..., T, 2, 3) ::Array{T, 2} a3 = f(rng..., T, b2, u3) ::Array{T, 2} a4 = f(rng..., T, (2, 3)) ::Array{T, 2} - @test size(a0) == () + if T <: Number + @test size(a0) == () + end @test size(a1) == (5,) @test size(a2) == size(a3) == size(a4) == (2, 3) if T <: AbstractFloat && f === rand @@ -359,6 +365,7 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) end for f! in [rand!, randn!, randexp!] for T in functypes[f!] + (T <: Tuple) && continue X = T == Bool ? T[0,1] : T[0,1,2] for A in (Vector{T}(undef, 5), Matrix{T}(undef, 2, 3), @@ -405,6 +412,10 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) @test_throws MethodError r(rng..., Number, (2,3)) @test_throws MethodError r(rng..., Any, 1) end + + # Test that you cannot call rand with a tuple type of unknown size or with isbits parameters + @test_throws ArgumentError rand(rng..., Tuple{Vararg{Int}}) + @test_throws TypeError rand(rng..., Tuple{1:2}) end function hist(X, n)