From f3fada9e4d7321b95c065490296d1499fe25ae02 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Mon, 5 Jun 2017 18:59:48 +0200 Subject: [PATCH 1/7] enable rand(::Union{AbstractSet,Associative}) --- base/random.jl | 29 ++++++++++++++++++----------- base/test.jl | 22 +++++++++++++++++++++- test/random.jl | 39 +++++++++++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/base/random.jl b/base/random.jl index e6f795b75378d..37dbe35b3e58b 100644 --- a/base/random.jl +++ b/base/random.jl @@ -362,9 +362,8 @@ function rand(r::AbstractRNG, t::Dict) Base.isslotfilled(t, i) && @inbounds return (t.keys[i] => t.vals[i]) end end -rand(t::Dict) = rand(GLOBAL_RNG, t) + rand(r::AbstractRNG, s::Set) = rand(r, s.dict).first -rand(s::Set) = rand(GLOBAL_RNG, s) function rand(r::AbstractRNG, s::IntSet) isempty(s) && throw(ArgumentError("collection must be non-empty")) @@ -378,7 +377,16 @@ function rand(r::AbstractRNG, s::IntSet) end end -rand(s::IntSet) = rand(GLOBAL_RNG, s) +function nth(iter, n::Integer)::eltype(iter) + for (i, x) in enumerate(iter) + i == n && return x + end +end +nth(iter::AbstractArray, n::Integer) = iter[n] + +rand(r::AbstractRNG, s::Union{Associative,AbstractSet}) = nth(s, rand(r, 1:length(s))) + +rand(s::Union{Associative,AbstractSet}) = rand(GLOBAL_RNG, s) ## Arrays of random numbers @@ -400,21 +408,20 @@ end rand!(A::AbstractArray, ::Type{X}) where {X} = rand!(GLOBAL_RNG, A, X) -function rand!(r::AbstractRNG, A::AbstractArray, s::Union{Dict,Set,IntSet}) +function rand!(r::AbstractRNG, A::AbstractArray, s::Union{Associative,AbstractSet}) for i in eachindex(A) @inbounds A[i] = rand(r, s) end A end -rand!(A::AbstractArray, s::Union{Dict,Set,IntSet}) = rand!(GLOBAL_RNG, A, s) +rand!(A::AbstractArray, s::Union{Associative,AbstractSet}) = rand!(GLOBAL_RNG, A, s) -rand(r::AbstractRNG, s::Dict{K,V}, dims::Dims) where {K,V} = rand!(r, Array{Pair{K,V}}(dims), s) -rand(r::AbstractRNG, s::Set{T}, dims::Dims) where {T} = rand!(r, Array{T}(dims), s) -rand(r::AbstractRNG, s::IntSet, dims::Dims) = rand!(r, Array{Int}(dims), s) -rand(r::AbstractRNG, s::Union{Dict,Set,IntSet}, dims::Integer...) = rand(r, s, convert(Dims, dims)) -rand(s::Union{Dict,Set,IntSet}, dims::Integer...) = rand(GLOBAL_RNG, s, convert(Dims, dims)) -rand(s::Union{Dict,Set,IntSet}, dims::Dims) = rand(GLOBAL_RNG, s, dims) +rand(r::AbstractRNG, s::Associative{K,V}, dims::Dims) where {K,V} = rand!(r, Array{Pair{K,V}}(dims), s) +rand(r::AbstractRNG, s::AbstractSet{T}, dims::Dims) where {T} = rand!(r, Array{T}(dims), s) +rand(r::AbstractRNG, s::Union{Associative,AbstractSet}, dims::Integer...) = rand(r, s, convert(Dims, dims)) +rand(s::Union{Associative,AbstractSet}, dims::Integer...) = rand(GLOBAL_RNG, s, convert(Dims, dims)) +rand(s::Union{Associative,AbstractSet}, dims::Dims) = rand(GLOBAL_RNG, s, dims) # MersenneTwister diff --git a/base/test.jl b/base/test.jl index 9180f27061f01..ad3d93a6e0990 100644 --- a/base/test.jl +++ b/base/test.jl @@ -18,7 +18,7 @@ export @testset # Legacy approximate testing functions, yet to be included export @inferred export detect_ambiguities -export GenericString +export GenericString, GenericSet, GenericDict #----------------------------------------------------------------------- @@ -1182,4 +1182,24 @@ Base.convert(::Type{GenericString}, s::AbstractString) = GenericString(s) Base.endof(s::GenericString) = endof(s.string) Base.next(s::GenericString, i::Int) = next(s.string, i) +""" +The `GenericSet` can be used to test generic set APIs that program to +the `AbstractSet` interface, in order to ensure that functions can work +with set types besides the standard `Set` and `IntSet` types. +""" +struct GenericSet{T} <: AbstractSet{T} + s::AbstractSet{T} +end + +""" +The `GenericDict` can be used to test generic dict APIs that program to +the `Associative` interface, in order to ensure that functions can work +with associative types besides the standard `Dict` type. +""" +struct GenericDict{K,V} <: Associative{K,V} + s::Associative{K,V} +end + +Base.get(s::GenericDict, x, y) = get(s.s, x, y) + end # module diff --git a/test/random.jl b/test/random.jl index 6752a68e65c15..d799c93407280 100644 --- a/test/random.jl +++ b/test/random.jl @@ -1,5 +1,22 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# should be in base/test.jl, but does not compile... + +for (G, A) in ((GenericSet, AbstractSet), + (GenericDict, Associative)) + @eval begin + Base.convert(::Type{$G}, s::$A) = $G(s) + Base.done(s::$G, state) = done(s.s, state) + Base.next(s::$G, state) = next(s.s, state) + end + for f in (:eltype, :isempty, :length, :start) + @eval begin + Base.$f(s::$G) = $f(s.s) + end + end +end + + # Issue #6573 srand(0); rand(); x = rand(384) @test find(x .== rand()) == [] @@ -309,13 +326,18 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()]) ftypes = [Float16, Float32, Float64] cftypes = [Complex32, Complex64, Complex128, ftypes...] types = [Bool, Char, Base.BitInteger_types..., ftypes...] - collections = [IntSet(rand(1:100, 20)) => Int, - Set(rand(Int, 20)) => Int, - Dict(zip(rand(Int,10), rand(Int, 10))) => Pair{Int,Int}, - 1:100 => Int, - rand(Int, 100) => Int, - Int => Int, - Float64 => Float64] + randset = Set(rand(Int, 20)) + randdict = Dict(zip(rand(Int,10), rand(Int, 10))) + collections = [IntSet(rand(1:100, 20)) => Int, + randset => Int, + GenericSet(randset) => Int, + randdict => Pair{Int,Int}, + GenericDict(randdict) => Pair{Int,Int}, + 1:100 => Int, + rand(Int, 100) => Int, + Int => Int, + Float64 => Float64] + b2 = big(2) u3 = UInt(3) for f in [rand, randn, randexp] @@ -352,7 +374,8 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()]) end end end - for C in [1:0, Dict(), Set(), IntSet(), Int[]] + for C in [1:0, Dict(), Set(), IntSet(), Int[], + GenericDict(Dict()), GenericSet(Set())] @test_throws ArgumentError rand(rng..., C) @test_throws ArgumentError rand(rng..., C, 5) end From a18368711e5094e9d313bde1c8bf975f14ae9ffc Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Tue, 6 Jun 2017 12:52:08 +0200 Subject: [PATCH 2/7] adjust indentation [ci skip] --- test/random.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/random.jl b/test/random.jl index d799c93407280..dace6bed7d732 100644 --- a/test/random.jl +++ b/test/random.jl @@ -4,15 +4,15 @@ for (G, A) in ((GenericSet, AbstractSet), (GenericDict, Associative)) - @eval begin - Base.convert(::Type{$G}, s::$A) = $G(s) - Base.done(s::$G, state) = done(s.s, state) - Base.next(s::$G, state) = next(s.s, state) - end - for f in (:eltype, :isempty, :length, :start) - @eval begin - Base.$f(s::$G) = $f(s.s) - end + @eval begin + Base.convert(::Type{$G}, s::$A) = $G(s) + Base.done(s::$G, state) = done(s.s, state) + Base.next(s::$G, state) = next(s.s, state) + end + for f in (:eltype, :isempty, :length, :start) + @eval begin + Base.$f(s::$G) = $f(s.s) + end end end From f1870222f0e8352228591dcaede115f81c5e3085 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Wed, 7 Jun 2017 11:57:58 +0200 Subject: [PATCH 3/7] warn about linear complexity (stevengj/ararslan) --- base/random.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/base/random.jl b/base/random.jl index 37dbe35b3e58b..247e01222a97f 100644 --- a/base/random.jl +++ b/base/random.jl @@ -257,13 +257,21 @@ globalRNG() = GLOBAL_RNG 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:n` or `['x','y','z']`), or -* a `Dict`, a `Set` or an `IntSet`, or +* an `Associative` or `AbstractSet` object, 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)), and to ``[0, 1)`` for floating point numbers; `S` defaults to [`Float64`](@ref). +!!! note + The complexity of `rand(rng, s::Union{Associative,AbstractSet})` + is linear in the length of `s`, unless an optimized method (with + constant complexity) is available (which is the case for `Dict`, + `Set` and `IntSet`). For more than a few calls, use `rand(rng, + collect(s))` instead (or `rand(rng, Dict(s))`, or `rand(rng, + Set(s))`). + ```julia-repl julia> rand(Int, 2) 2-element Array{Int64,1}: From 078f253f5c528b418f79ba02ec9144f4bc042d5f Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Wed, 7 Jun 2017 12:14:43 +0200 Subject: [PATCH 4/7] avoid linear complexity for array operation --- base/random.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/random.jl b/base/random.jl index 247e01222a97f..e3582f3ffe9c5 100644 --- a/base/random.jl +++ b/base/random.jl @@ -416,13 +416,16 @@ end rand!(A::AbstractArray, ::Type{X}) where {X} = rand!(GLOBAL_RNG, A, X) -function rand!(r::AbstractRNG, A::AbstractArray, s::Union{Associative,AbstractSet}) +function rand!(r::AbstractRNG, A::AbstractArray, s::Union{Dict,Set,IntSet}) for i in eachindex(A) @inbounds A[i] = rand(r, s) end A end +# avoid linear complexity for repeated calls with generic containers +rand!(r::AbstractRNG, A::AbstractArray, s::Union{Associative,AbstractSet}) = rand!(r, A, collect(s)) + rand!(A::AbstractArray, s::Union{Associative,AbstractSet}) = rand!(GLOBAL_RNG, A, s) rand(r::AbstractRNG, s::Associative{K,V}, dims::Dims) where {K,V} = rand!(r, Array{Pair{K,V}}(dims), s) From 2ab612ebc5a4c273b65d2b42703cdeb2b5de71fe Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Thu, 8 Jun 2017 12:25:33 +0200 Subject: [PATCH 5/7] solve compile error --- base/test.jl | 16 ++++++++++++++++ test/random.jl | 17 ----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/base/test.jl b/base/test.jl index ad3d93a6e0990..80eacffa61642 100644 --- a/base/test.jl +++ b/base/test.jl @@ -11,6 +11,8 @@ test set that throws on the first failure. Users can choose to wrap their tests in (possibly nested) test sets that will store results and summarize them at the end of the test set with `@testset`. """ +:Test # cf. #22288 + module Test export @test, @test_throws, @test_broken, @test_skip, @test_warn, @test_nowarn @@ -1200,6 +1202,20 @@ struct GenericDict{K,V} <: Associative{K,V} s::Associative{K,V} end +for (G, A) in ((GenericSet, AbstractSet), + (GenericDict, Associative)) + @eval begin + Base.convert(::Type{$G}, s::$A) = $G(s) + Base.done(s::$G, state) = done(s.s, state) + Base.next(s::$G, state) = next(s.s, state) + end + for f in (:eltype, :isempty, :length, :start) + @eval begin + Base.$f(s::$G) = $f(s.s) + end + end +end + Base.get(s::GenericDict, x, y) = get(s.s, x, y) end # module diff --git a/test/random.jl b/test/random.jl index dace6bed7d732..5672ff3066e6d 100644 --- a/test/random.jl +++ b/test/random.jl @@ -1,22 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# should be in base/test.jl, but does not compile... - -for (G, A) in ((GenericSet, AbstractSet), - (GenericDict, Associative)) - @eval begin - Base.convert(::Type{$G}, s::$A) = $G(s) - Base.done(s::$G, state) = done(s.s, state) - Base.next(s::$G, state) = next(s.s, state) - end - for f in (:eltype, :isempty, :length, :start) - @eval begin - Base.$f(s::$G) = $f(s.s) - end - end -end - - # Issue #6573 srand(0); rand(); x = rand(384) @test find(x .== rand()) == [] From 5c195d849b90e2ad9574a2e15c37d2d13b474027 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Thu, 8 Jun 2017 12:33:40 +0200 Subject: [PATCH 6/7] update doc (tkelman & ararslan) --- base/random.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/base/random.jl b/base/random.jl index e3582f3ffe9c5..02bea3d8a2e2a 100644 --- a/base/random.jl +++ b/base/random.jl @@ -264,13 +264,7 @@ Pick a random element or array of random elements from the set of values specifi `S` defaults to [`Float64`](@ref). -!!! note - The complexity of `rand(rng, s::Union{Associative,AbstractSet})` - is linear in the length of `s`, unless an optimized method (with - constant complexity) is available (which is the case for `Dict`, - `Set` and `IntSet`). For more than a few calls, use `rand(rng, - collect(s))` instead (or `rand(rng, Dict(s))`, or `rand(rng, - Set(s))`). +# Examples ```julia-repl julia> rand(Int, 2) @@ -280,6 +274,14 @@ julia> rand(Int, 2) julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4)) 1=>2 + +!!! note + The complexity of `rand(rng, s::Union{Associative,AbstractSet})` + is linear in the length of `s`, unless an optimized method with + constant complexity is available, which is the case for `Dict`, + `Set` and `IntSet`. For more than a few calls, use `rand(rng, + collect(s))` instead, or either `rand(rng, Dict(s))` or `rand(rng, + Set(s))` as appropriate. ``` """ @inline rand() = rand(GLOBAL_RNG, CloseOpen) From 53e6f69745a68c3de2e21e9627ce065331e7ff8e Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Thu, 8 Jun 2017 12:43:29 +0200 Subject: [PATCH 7/7] oups, bad quotes --- base/random.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/random.jl b/base/random.jl index 02bea3d8a2e2a..f95e0ed7354a4 100644 --- a/base/random.jl +++ b/base/random.jl @@ -274,6 +274,7 @@ julia> rand(Int, 2) julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4)) 1=>2 +``` !!! note The complexity of `rand(rng, s::Union{Associative,AbstractSet})` @@ -282,7 +283,6 @@ julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4)) `Set` and `IntSet`. For more than a few calls, use `rand(rng, collect(s))` instead, or either `rand(rng, Dict(s))` or `rand(rng, Set(s))` as appropriate. -``` """ @inline rand() = rand(GLOBAL_RNG, CloseOpen) @inline rand(T::Type) = rand(GLOBAL_RNG, T)