Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace the API of replace #25697

Merged
merged 2 commits into from
Feb 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 33 additions & 65 deletions base/set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

"""
Expand All @@ -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
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we can drop K now too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, and I also wanted to check if with this new version we could get rid of this intermediate function altogether without performance hit, but was lacking time. I will come to it eventually.

@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

"""
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)))
Expand All @@ -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
Expand All @@ -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
89 changes: 38 additions & 51 deletions test/sets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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!("")
Expand Down