diff --git a/NEWS.md b/NEWS.md index f4966babc60ae..5768a1b00ffd2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -56,6 +56,8 @@ Standard library changes * When seeding RNGs provided by `Random`, negative integer seeds can now be used ([#51416]). +* `rand` now supports sampling over `Pair` types ([#28705]). + #### REPL * Tab complete hints now show in lighter text while typing in the repl. To disable diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index e3f5cae5a430e..432e32a4de691 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -330,8 +330,11 @@ Pick a random element or array of random elements from the set of values specifi + 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. + + 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 + + + a `Pair` type, e.g. `Pair{X, Y}` such that `rand` is defined for `X` and `Y`, + in which case random pairs are produced. `S` defaults to [`Float64`](@ref). diff --git a/stdlib/Random/src/generation.jl b/stdlib/Random/src/generation.jl index 1e3edea21c03b..afc17dd7079e2 100644 --- a/stdlib/Random/src/generation.jl +++ b/stdlib/Random/src/generation.jl @@ -182,6 +182,19 @@ 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 +### random pairs + +function Sampler(::Type{RNG}, ::Type{Pair{A, B}}, n::Repetition) where {RNG<:AbstractRNG, A, B} + sp1 = Sampler(RNG, A, n) + sp2 = A === B ? sp1 : Sampler(RNG, B, n) + SamplerTag{Ref{Pair{A,B}}}(sp1 => sp2) # Ref so that the gentype is Pair{A, B} + # in SamplerTag's constructor +end + +rand(rng::AbstractRNG, sp::SamplerTag{<:Ref{<:Pair}}) = + rand(rng, sp.data.first) => rand(rng, sp.data.second) + + ## Generate random integer within a range ### BitInteger diff --git a/stdlib/Random/test/runtests.jl b/stdlib/Random/test/runtests.jl index e885d6f69aa4a..f49187045b492 100644 --- a/stdlib/Random/test/runtests.jl +++ b/stdlib/Random/test/runtests.jl @@ -285,7 +285,8 @@ end for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) ftypes = [Float16, Float32, Float64, FakeFloat64, BigFloat] cftypes = [ComplexF16, ComplexF32, ComplexF64, ftypes...] - types = [Bool, Char, BigFloat, Tuple{Bool, Tuple{Int, Char}}, Base.BitInteger_types..., cftypes...] + types = [Bool, Char, BigFloat, Tuple{Bool, Tuple{Int, Char}}, Pair{Int8, UInt32}, + Base.BitInteger_types..., cftypes...] randset = Set(rand(Int, 20)) randdict = Dict(zip(rand(Int,10), rand(Int, 10))) @@ -365,7 +366,7 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) end for f! in [rand!, randn!, randexp!] for T in functypes[f!] - (T <: Tuple) && continue + (T <: Tuple || T <: Pair) && continue X = T == Bool ? T[0,1] : T[0,1,2] for A in (Vector{T}(undef, 5), Matrix{T}(undef, 2, 3), @@ -1180,3 +1181,16 @@ end @test Random.hash_seed(map(UInt64, seed32)) == hash32 @test hash32 ∉ keys(vseeds) end + +@testset "rand(::Type{<:Pair})" begin + @test rand(Pair{Int, Int}) isa Pair{Int, Int} + @test rand(Pair{Int, Float64}) isa Pair{Int, Float64} + @test rand(Pair{Int, Float64}, 3) isa Array{Pair{Int, Float64}} + + # test that making an array out of a sampler works + # (i.e. that gentype(sp) is correct) + sp = Random.Sampler(AbstractRNG, Pair{Bool, Char}) + xs = rand(sp, 3) + @test xs isa Vector{Pair{Bool, Char}} + @test length(xs) == 3 +end