From dce9d05743b4ea8edcaebfc5a71b5d53bf1da34c Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Fri, 2 Feb 2018 01:37:40 +0700 Subject: [PATCH] replace the API of replace (#25697) Make nothing not special. --- base/set.jl | 98 ++++++++++++++++++---------------------------------- test/sets.jl | 89 ++++++++++++++++++++--------------------------- 2 files changed, 71 insertions(+), 116 deletions(-) diff --git a/base/set.jl b/base/set.jl index d12364f318448..6e520763e9c80 100644 --- a/base/set.jl +++ b/base/set.jl @@ -565,7 +565,7 @@ convert(::Type{T}, s::AbstractSet) where {T<:AbstractSet} = T(s) ## replace/replace! ## # to make replace/replace! work for a new container type Cont, only -# replace!(prednew::Callable, A::Cont; count::Integer=typemax(Int)) +# replace!(new::Callable, A::Cont; count::Integer=typemax(Int)) # has to be implemented """ @@ -591,15 +591,14 @@ Set([0, 2, 3]) """ replace!(A, old_new::Pair...; count::Integer=typemax(Int)) = _replace!(A, eltype(A), count, old_new) -# we use this wrapper because using directly eltype(A) as the type -# parameter below for Some degrades performance function _replace!(A, ::Type{K}, count::Integer, old_new::Tuple{Vararg{Pair}}) where K - @inline function prednew(x) + @inline function new(x) for o_n in old_new - first(o_n) == x && return Some{K}(last(o_n)) + first(o_n) == x && return last(o_n) end + return x # no replace end - replace!(prednew, A, count=count) + replace!(new, A, count=count) end """ @@ -621,37 +620,26 @@ julia> replace!(isodd, A, 0, count=2) ``` """ replace!(pred::Callable, A, new; count::Integer=typemax(Int)) = - replace!(x -> if pred(x) Some(new) end, A, count=count) + replace!(x -> ifelse(pred(x), new, x), A, count=count) """ - replace!(prednew::Function, A; [count::Integer]) + replace!(new::Function, A; [count::Integer]) -For each value `x` in `A`, `prednew(x)` is called and must -return either `nothing`, in which case no replacement occurs, -or a value, possibly wrapped as a [`Some`](@ref) object, which -will be used as a replacement for `x`. +Replace each element `x` in collection `A` by `new(x)`. +If `count` is specified, then replace at most `count` values in total +(replacements being defined as `new(x) !== x`). # Examples ```jldoctest -julia> replace!(x -> isodd(x) ? 2x : nothing, [1, 2, 3, 4]) +julia> replace!(x -> isodd(x) ? 2x : x, [1, 2, 3, 4]) 4-element Array{Int64,1}: 2 2 6 4 -julia> replace!(Union{Int,Nothing}[0, 1, 2, nothing, 4], count=2) do x - x !== nothing && iseven(x) ? Some(nothing) : nothing - end -5-element Array{Union{Nothing,Int64},1}: - nothing - 1 - nothing - nothing - 4 - julia> replace!(Dict(1=>2, 3=>4)) do kv - if first(kv) < 3; first(kv)=>3 end + first(kv) < 3 ? first(kv)=>3 : kv end Dict{Int64,Int64} with 2 entries: 3 => 4 @@ -661,10 +649,10 @@ julia> replace!(x->2x, Set([3, 6])) Set([6, 12]) ``` """ -function replace!(prednew::Callable, A::Union{AbstractArray,AbstractDict,AbstractSet}; +function replace!(new::Callable, A::Union{AbstractArray,AbstractDict,AbstractSet}; count::Integer=typemax(Int)) count < 0 && throw(DomainError(count, "`count` must not be negative")) - count != 0 && _replace!(prednew, A, min(count, typemax(Int)) % Int) + count != 0 && _replace!(new, A, min(count, typemax(Int)) % Int) A end @@ -706,44 +694,31 @@ julia> replace(isodd, [1, 2, 3, 1], 0, count=2) ``` """ replace(pred::Callable, A, new; count::Integer=typemax(Int)) = - replace!(x -> if pred(x) Some(new) end, copy(A), count=count) + replace!(pred, copy(A), new, count=count) """ - replace(prednew::Function, A; [count::Integer]) + replace(new::Function, A; [count::Integer]) -Return a copy of `A` where for each value `x` in `A`, `prednew(x)` is called -and must return either `nothing`, in which case no replacement occurs, -or a value, possibly wrapped as a [`Some`](@ref) object, which -will be used as a replacement for `x`. +Return a copy of `A` where each value `x` in `A` is replaced by `new(x)` # Examples ```jldoctest -julia> replace(x -> isodd(x) ? 2x : nothing, [1, 2, 3, 4]) +julia> replace(x -> isodd(x) ? 2x : x, [1, 2, 3, 4]) 4-element Array{Int64,1}: 2 2 6 4 -julia> replace(Union{Int,Nothing}[0, 1, 2, nothing, 4], count=2) do x - x !== nothing && iseven(x) ? Some(nothing) : nothing - end -5-element Array{Union{Nothing,Int64},1}: - nothing - 1 - nothing - nothing - 4 - julia> replace(Dict(1=>2, 3=>4)) do kv - if first(kv) < 3; first(kv)=>3 end + first(kv) < 3 ? first(kv)=>3 : kv end Dict{Int64,Int64} with 2 entries: 3 => 4 1 => 3 ``` """ -replace(prednew::Callable, A; count::Integer=typemax(Int)) = replace!(prednew, copy(A), count=count) +replace(new::Callable, A; count::Integer=typemax(Int)) = replace!(new, copy(A), count=count) # Handle ambiguities replace!(a::Callable, b::Pair; count::Integer=-1) = throw(MethodError(replace!, (a, b))) @@ -758,19 +733,15 @@ replace(a::AbstractString, b::Pair, c::Pair) = throw(MethodError(replace, (a, b, askey(k, ::AbstractDict) = k.first askey(k, ::AbstractSet) = k -function _replace_update_dict!(repl::Vector{<:Pair}, x, y::Some) - push!(repl, x => y.value) - true -end - -_replace_update_dict!(repl::Vector{<:Pair}, x, ::Nothing) = false -_replace_update_dict!(repl::Vector{<:Pair}, x, y) = _replace_update_dict!(repl, x, Some(y)) - -function _replace!(prednew::Callable, A::Union{AbstractDict,AbstractSet}, count::Int) +function _replace!(new::Callable, A::Union{AbstractDict,AbstractSet}, count::Int) repl = Pair{eltype(A),eltype(A)}[] c = 0 for x in A - c += _replace_update_dict!(repl, x, prednew(x)) + y = new(x) + if x !== y + push!(repl, x => y) + c += 1 + end c == count && break end for oldnew in repl @@ -783,18 +754,15 @@ end ### AbstractArray -function _replace_update!(A::AbstractArray, i::Integer, y::Some) - @inbounds A[i] = y.value - true -end - -_replace_update!(A::AbstractArray, i::Integer, ::Nothing) = false -_replace_update!(A::AbstractArray, i::Integer, y) = _replace_update!(A, i, Some(y)) - -function _replace!(prednew::Callable, A::AbstractArray, count::Int) +function _replace!(new::Callable, A::AbstractArray, count::Int) c = 0 for i in eachindex(A) - c += _replace_update!(A, i, prednew(A[i])) + @inbounds Ai = A[i] + y = new(Ai) + if Ai !== y + @inbounds A[i] = y + c += 1 + end c == count && break end end diff --git a/test/sets.jl b/test/sets.jl index 6509177a82afb..4c09c72b07026 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -492,65 +492,52 @@ end end @testset "replace! & replace" begin - maybe1(v, p) = if p Some(v) end - maybe2(v, p) = if p v end - - for maybe = (maybe1, maybe2) - a = [1, 2, 3, 1] - @test replace(x->maybe(2x, iseven(x)), a) == [1, 4, 3, 1] - @test replace!(x->maybe(2x, iseven(x)), a) === a - @test a == [1, 4, 3, 1] - @test replace(a, 1=>0) == [0, 4, 3, 0] - for count = (1, 0x1, big(1)) - @test replace(a, 1=>0, count=count) == [0, 4, 3, 1] - end - @test replace!(a, 1=>2) === a - @test a == [2, 4, 3, 2] - @test replace!(x->2x, a, count=0x2) == [4, 8, 3, 2] - - d = Dict(1=>2, 3=>4) - @test replace(x->x.first > 2, d, 0=>0) == Dict(1=>2, 0=>0) - @test replace!(x->maybe(x.first=>2*x.second, x.first > 2), d) === d - @test d == Dict(1=>2, 3=>8) - @test replace(d, (3=>8)=>(0=>0)) == Dict(1=>2, 0=>0) - @test replace!(d, (3=>8)=>(2=>2)) === d - @test d == Dict(1=>2, 2=>2) - for count = (1, 0x1, big(1)) - @test replace(x->x.second == 2, d, 0=>0, count=count) in [Dict(1=>2, 0=>0), - Dict(2=>2, 0=>0)] - end - s = Set([1, 2, 3]) - @test replace(x->maybe(2x, x>1), s) == Set([1, 4, 6]) - for count = (1, 0x1, big(1)) - @test replace(x->maybe(2x, x>1), s, count=count) in [Set([1, 4, 3]), Set([1, 2, 6])] - end - @test replace(s, 1=>4) == Set([2, 3, 4]) - @test replace!(s, 1=>2) === s - @test s == Set([2, 3]) - @test replace!(x->2x, s, count=0x1) in [Set([4, 3]), Set([2, 6])] + a = [1, 2, 3, 1] + @test replace(x -> iseven(x) ? 2x : x, a) == [1, 4, 3, 1] + @test replace!(x -> iseven(x) ? 2x : x, a) === a + @test a == [1, 4, 3, 1] + @test replace(a, 1=>0) == [0, 4, 3, 0] + for count = (1, 0x1, big(1)) + @test replace(a, 1=>0, count=count) == [0, 4, 3, 1] + end + @test replace!(a, 1=>2) === a + @test a == [2, 4, 3, 2] + @test replace!(x->2x, a, count=0x2) == [4, 8, 3, 2] + + d = Dict(1=>2, 3=>4) + @test replace(x->x.first > 2, d, 0=>0) == Dict(1=>2, 0=>0) + @test replace!(x -> x.first > 2 ? x.first=>2*x.second : x, d) === d + @test d == Dict(1=>2, 3=>8) + @test replace(d, (3=>8)=>(0=>0)) == Dict(1=>2, 0=>0) + @test replace!(d, (3=>8)=>(2=>2)) === d + @test d == Dict(1=>2, 2=>2) + for count = (1, 0x1, big(1)) + @test replace(x->x.second == 2, d, 0=>0, count=count) in [Dict(1=>2, 0=>0), + Dict(2=>2, 0=>0)] + end + s = Set([1, 2, 3]) + @test replace(x -> x > 1 ? 2x : x, s) == Set([1, 4, 6]) + for count = (1, 0x1, big(1)) + @test replace(x -> x > 1 ? 2x : x, s, count=count) in [Set([1, 4, 3]), Set([1, 2, 6])] + end + @test replace(s, 1=>4) == Set([2, 3, 4]) + @test replace!(s, 1=>2) === s + @test s == Set([2, 3]) + @test replace!(x->2x, s, count=0x1) in [Set([4, 3]), Set([2, 6])] - for count = (0, 0x0, big(0)) - @test replace([1, 2], 1=>0, 2=>0, count=count) == [1, 2] # count=0 --> no replacements - end + for count = (0, 0x0, big(0)) + @test replace([1, 2], 1=>0, 2=>0, count=count) == [1, 2] # count=0 --> no replacements end + # test collisions with AbstractSet/AbstractDict @test replace!(x->2x, Set([3, 6])) == Set([6, 12]) @test replace!(x->2x, Set([1:20;])) == Set([2:2:40;]) @test replace!(kv -> (2kv[1] => kv[2]), Dict(1=>2, 2=>4, 4=>8, 8=>16)) == Dict(2=>2, 4=>4, 8=>8, 16=>16) - # test Some(nothing) + # test nothing & Some(nothing) (should behave as any other value) a = [1, 2, nothing, 4] - @test replace(x -> x === nothing ? 0 : Some(nothing), a) == [nothing, nothing, 0, nothing] - @test replace(x -> x === nothing ? 0 : nothing, a) == [1, 2, 0, 4] - @test replace!(x -> x !== nothing ? Some(nothing) : nothing, a) == [nothing, nothing, nothing, nothing] - @test replace(iseven, Any[1, 2, 3, 4], nothing) == [1, nothing, 3, nothing] - @test replace(Any[1, 2, 3, 4], 1=>nothing, 3=>nothing) == [nothing, 2, nothing, 4] - s = Set([1, 2, nothing, 4]) - @test replace(x -> x === nothing ? 0 : Some(nothing), s) == Set([0, nothing]) - @test replace(x -> x === nothing ? 0 : nothing, s) == Set([1, 2, 0, 4]) - @test replace(x -> x !== nothing ? Some(nothing) : nothing, s) == Set([nothing]) - @test replace(iseven, Set(Any[1, 2, 3, 4]), nothing) == Set([1, nothing, 3, nothing]) - @test replace(Set(Any[1, 2, 3, 4]), 1=>nothing, 3=>nothing) == Set([nothing, 2, nothing, 4]) + @test replace(x -> x === nothing ? Some(nothing) : nothing, a) == [nothing, nothing, Some(nothing), nothing] + @test replace(x -> x === nothing ? Some(nothing) : nothing, Set(a)) == Set([Some(nothing), nothing]) # avoid recursive call issue #25384 @test_throws MethodError replace!("")