Revision 3711749292ba9c29ad2e3b9eaee90995f8c8290a authored by Keno Fischer on 11 October 2023, 14:41:22 UTC, committed by GitHub on 11 October 2023, 14:41:22 UTC
This should be NFC and is intended to allow the optimizer to delete :enter statements (by replacing them with `nothing`), without leaving dangling `:leave`s around. This is accomplished by having `leave` take (a variable number of) `:enter` tokens (that are already being used by `:pop_exception`). The semantics are that a literal `nothing` or an SSAValue pointing to a `nothing` statement are ignored, and one exception handler is popped for each remaining argument. The actual value of the token is ignored, except that the verifier asserts that it belongs to an `:enter`. Note that we don't need to do the same for :pop_exception, because the token generated by an `:enter` is semantically only in scope for :pop_exception during its catch block. If we determine the `:enter` is dead, then its catch block is guaranteed to not be executed and will be deleted wholesale by cfg liveness. I was considering doing something fancier where :leave is changed back to taking an integer after optimization, but the case where the IR size is bigger after this change (when we are `:leave`ing many handlers) is fairly rare and likely not worth the additional complexity or time cost to do anything special. If it does show up in size benchmarks, I'd rather give `:leave` a special, compact encoding.
1 parent 8180240
set.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
Set{T} <: AbstractSet{T}
`Set`s are mutable containers that provide fast membership testing.
`Set`s have efficient implementations of set operations such as `in`, `union` and `intersect`.
Elements in a `Set` are unique, as determined by the elements' definition of `isequal`.
The order of elements in a `Set` is an implementation detail and cannot be relied on.
See also: [`AbstractSet`](@ref), [`BitSet`](@ref), [`Dict`](@ref),
[`push!`](@ref), [`empty!`](@ref), [`union!`](@ref), [`in`](@ref), [`isequal`](@ref)
# Examples
```jldoctest; filter = r"^ '.'"ma
julia> s = Set("aaBca")
Set{Char} with 3 elements:
'a'
'c'
'B'
julia> push!(s, 'b')
Set{Char} with 4 elements:
'a'
'b'
'B'
'c'
julia> s = Set([NaN, 0.0, 1.0, 2.0]);
julia> -0.0 in s # isequal(0.0, -0.0) is false
false
julia> NaN in s # isequal(NaN, NaN) is true
true
```
"""
struct Set{T} <: AbstractSet{T}
dict::Dict{T,Nothing}
global _Set(dict::Dict{T,Nothing}) where {T} = new{T}(dict)
end
Set{T}() where {T} = _Set(Dict{T,Nothing}())
Set{T}(s::Set{T}) where {T} = _Set(Dict{T,Nothing}(s.dict))
Set{T}(itr) where {T} = union!(Set{T}(), itr)
Set() = Set{Any}()
function Set{T}(s::KeySet{T, <:Dict{T}}) where {T}
d = s.dict
slots = copy(d.slots)
keys = copy(d.keys)
vals = similar(d.vals, Nothing)
_Set(Dict{T,Nothing}(slots, keys, vals, d.ndel, d.count, d.age, d.idxfloor, d.maxprobe))
end
Set(itr) = _Set(itr, IteratorEltype(itr))
_Set(itr, ::HasEltype) = Set{eltype(itr)}(itr)
function _Set(itr, ::EltypeUnknown)
T = @default_eltype(itr)
(isconcretetype(T) || T === Union{}) || return grow_to!(Set{T}(), itr)
return Set{T}(itr)
end
empty(s::AbstractSet{T}, ::Type{U}=T) where {T,U} = Set{U}()
# return an empty set with eltype T, which is mutable (can be grown)
# by default, a Set is returned
emptymutable(s::AbstractSet{T}, ::Type{U}=T) where {T,U} = Set{U}()
_similar_for(c::AbstractSet, ::Type{T}, itr, isz, len) where {T} = empty(c, T)
function show(io::IO, s::Set)
if isempty(s)
if get(io, :typeinfo, Any) == typeof(s)
print(io, "Set()")
else
show(io, typeof(s))
print(io, "()")
end
else
print(io, "Set(")
show_vector(io, s)
print(io, ')')
end
end
isempty(s::Set) = isempty(s.dict)
length(s::Set) = length(s.dict)
in(x, s::Set) = haskey(s.dict, x)
# This avoids hashing and probing twice and it works the same as
# in!(x, s::Set) = in(x, s) ? true : (push!(s, x); false)
function in!(x, s::Set)
idx, sh = ht_keyindex2_shorthash!(s.dict, x)
idx > 0 && return true
_setindex!(s.dict, nothing, x, -idx, sh)
return false
end
push!(s::Set, x) = (s.dict[x] = nothing; s)
pop!(s::Set, x) = (pop!(s.dict, x); x)
pop!(s::Set, x, default) = (x in s ? pop!(s, x) : default)
function pop!(s::Set)
isempty(s) && throw(ArgumentError("set must be non-empty"))
return pop!(s.dict)[1]
end
delete!(s::Set, x) = (delete!(s.dict, x); s)
copy(s::Set) = copymutable(s)
copymutable(s::Set{T}) where {T} = Set{T}(s)
# Set is the default mutable fall-back
copymutable(s::AbstractSet{T}) where {T} = Set{T}(s)
sizehint!(s::Set, newsz) = (sizehint!(s.dict, newsz); s)
empty!(s::Set) = (empty!(s.dict); s)
rehash!(s::Set) = (rehash!(s.dict); s)
iterate(s::Set, i...) = iterate(KeySet(s.dict), i...)
# In case the size(s) is smaller than size(t) its more efficient to iterate through
# elements of s instead and only delete the ones also contained in t.
# The threshold for this decision boils down to a tradeoff between
# size(s) * cost(in() + delete!()) ≶ size(t) * cost(delete!())
# Empirical observations on Ints point towards a threshold of 0.8.
# To be on the safe side (e.g. cost(in) >>> cost(delete!) ) a
# conservative threshold of 0.5 was chosen.
function setdiff!(s::Set, t::Set)
if 2 * length(s) < length(t)
for x in s
x in t && delete!(s, x)
end
else
for x in t
delete!(s, x)
end
end
return s
end
"""
unique(itr)
Return an array containing only the unique elements of collection `itr`,
as determined by [`isequal`](@ref), in the order that the first of each
set of equivalent elements originally appears. The element type of the
input is preserved.
See also: [`unique!`](@ref), [`allunique`](@ref), [`allequal`](@ref).
# Examples
```jldoctest
julia> unique([1, 2, 6, 2])
3-element Vector{Int64}:
1
2
6
julia> unique(Real[1, 1.0, 2])
2-element Vector{Real}:
1
2
```
"""
function unique(itr)
if isa(IteratorEltype(itr), HasEltype)
T = eltype(itr)
out = Vector{T}()
seen = Set{T}()
for x in itr
!in!(x, seen) && push!(out, x)
end
return out
end
T = @default_eltype(itr)
y = iterate(itr)
y === nothing && return T[]
x, i = y
S = typeof(x)
R = isconcretetype(T) ? T : S
return _unique_from(itr, R[x], Set{R}((x,)), i)
end
_unique_from(itr, out, seen, i) = unique_from(itr, out, seen, i)
@inline function unique_from(itr, out::Vector{T}, seen, i) where T
while true
y = iterate(itr, i)
y === nothing && break
x, i = y
S = typeof(x)
if !(S === T || S <: T)
R = promote_typejoin(S, T)
seenR = convert(Set{R}, seen)
outR = convert(Vector{R}, out)
!in!(x, seenR) && push!(outR, x)
return _unique_from(itr, outR, seenR, i)
end
!in!(x, seen) && push!(out, x)
end
return out
end
unique(r::AbstractRange) = allunique(r) ? r : oftype(r, r[begin:begin])
"""
unique(f, itr)
Return an array containing one value from `itr` for each unique value produced by `f`
applied to elements of `itr`.
# Examples
```jldoctest
julia> unique(x -> x^2, [1, -1, 3, -3, 4])
3-element Vector{Int64}:
1
3
4
```
This functionality can also be used to extract the *indices* of the first
occurrences of unique elements in an array:
```jldoctest
julia> a = [3.1, 4.2, 5.3, 3.1, 3.1, 3.1, 4.2, 1.7];
julia> i = unique(i -> a[i], eachindex(a))
4-element Vector{Int64}:
1
2
3
8
julia> a[i]
4-element Vector{Float64}:
3.1
4.2
5.3
1.7
julia> a[i] == unique(a)
true
```
"""
function unique(f, C; seen::Union{Nothing,Set}=nothing)
out = Vector{eltype(C)}()
if seen !== nothing
for x in C
!in!(f(x), seen) && push!(out, x)
end
return out
end
s = iterate(C)
if s === nothing
return out
end
(x, i) = s
y = f(x)
seen = Set{typeof(y)}()
push!(seen, y)
push!(out, x)
return _unique!(f, out, C, seen, i)
end
function _unique!(f, out::AbstractVector, C, seen::Set, i)
s = iterate(C, i)
while s !== nothing
(x, i) = s
y = f(x)
if y ∉ seen
push!(out, x)
if y isa eltype(seen)
push!(seen, y)
else
seen2 = convert(Set{promote_typejoin(eltype(seen), typeof(y))}, seen)
push!(seen2, y)
return _unique!(f, out, C, seen2, i)
end
end
s = iterate(C, i)
end
return out
end
"""
unique!(f, A::AbstractVector)
Selects one value from `A` for each unique value produced by `f` applied to
elements of `A`, then return the modified A.
!!! compat "Julia 1.1"
This method is available as of Julia 1.1.
# Examples
```jldoctest
julia> unique!(x -> x^2, [1, -1, 3, -3, 4])
3-element Vector{Int64}:
1
3
4
julia> unique!(n -> n%3, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6])
3-element Vector{Int64}:
5
1
9
julia> unique!(iseven, [2, 3, 5, 7, 9])
2-element Vector{Int64}:
2
3
```
"""
function unique!(f, A::AbstractVector; seen::Union{Nothing,Set}=nothing)
if length(A) <= 1
return A
end
i = firstindex(A)::Int
x = @inbounds A[i]
y = f(x)
if seen === nothing
seen = Set{typeof(y)}()
end
push!(seen, y)
return _unique!(f, A, seen, i, i+1)
end
function _unique!(f, A::AbstractVector, seen::Set, current::Integer, i::Integer)
while i <= lastindex(A)
x = @inbounds A[i]
y = f(x)
if y ∉ seen
current += 1
@inbounds A[current] = x
if y isa eltype(seen)
push!(seen, y)
else
seen2 = convert(Set{promote_typejoin(eltype(seen), typeof(y))}, seen)
push!(seen2, y)
return _unique!(f, A, seen2, current, i+1)
end
end
i += 1
end
return resize!(A, current - firstindex(A)::Int + 1)::typeof(A)
end
# If A is not grouped, then we will need to keep track of all of the elements that we have
# seen so far.
_unique!(A::AbstractVector) = unique!(identity, A::AbstractVector)
# If A is grouped, so that each unique element is in a contiguous group, then we only
# need to keep track of one element at a time. We replace the elements of A with the
# unique elements that we see in the order that we see them. Once we have iterated
# through A, we resize A based on the number of unique elements that we see.
function _groupedunique!(A::AbstractVector)
isempty(A) && return A
idxs = eachindex(A)
y = first(A)
# We always keep the first element
T = NTuple{2,Any} # just to eliminate `iterate(idxs)::Nothing` candidate
it = iterate(idxs, (iterate(idxs)::T)[2])
count = 1
for x in Iterators.drop(A, 1)
if !isequal(x, y)
it = it::T
y = A[it[1]] = x
count += 1
it = iterate(idxs, it[2])
end
end
resize!(A, count)::typeof(A)
end
"""
unique!(A::AbstractVector)
Remove duplicate items as determined by [`isequal`](@ref), then return the modified `A`.
`unique!` will return the elements of `A` in the order that they occur. If you do not care
about the order of the returned data, then calling `(sort!(A); unique!(A))` will be much
more efficient as long as the elements of `A` can be sorted.
# Examples
```jldoctest
julia> unique!([1, 1, 1])
1-element Vector{Int64}:
1
julia> A = [7, 3, 2, 3, 7, 5];
julia> unique!(A)
4-element Vector{Int64}:
7
3
2
5
julia> B = [7, 6, 42, 6, 7, 42];
julia> sort!(B); # unique! is able to process sorted data much more efficiently.
julia> unique!(B)
3-element Vector{Int64}:
6
7
42
```
"""
function unique!(itr)
if isa(itr, AbstractVector)
if OrderStyle(eltype(itr)) === Ordered()
(issorted(itr) || issorted(itr, rev=true)) && return _groupedunique!(itr)
end
end
isempty(itr) && return itr
return _unique!(itr)
end
"""
allunique(itr) -> Bool
Return `true` if all values from `itr` are distinct when compared with [`isequal`](@ref).
See also: [`unique`](@ref), [`issorted`](@ref), [`allequal`](@ref).
# Examples
```jldoctest
julia> allunique([1, 2, 3])
true
julia> allunique([1, 2, 1, 2])
false
julia> allunique(Real[1, 1.0, 2])
false
julia> allunique([NaN, 2.0, NaN, 4.0])
false
```
"""
function allunique(C)
if haslength(C)
length(C) < 2 && return true
length(C) < 32 && return _indexed_allunique(collect(C))
end
return _hashed_allunique(C)
end
function _hashed_allunique(C)
seen = Set{eltype(C)}()
x = iterate(C)
if haslength(C) && length(C) > 1000
for i in OneTo(1000)
v, s = something(x)
in!(v, seen) && return false
x = iterate(C, s)
end
sizehint!(seen, length(C))
end
while x !== nothing
v, s = x
in!(v, seen) && return false
x = iterate(C, s)
end
return true
end
allunique(::Union{AbstractSet,AbstractDict}) = true
allunique(r::AbstractRange) = !iszero(step(r)) || length(r) <= 1
allunique(A::StridedArray) = length(A) < 32 ? _indexed_allunique(A) : _hashed_allunique(A)
function _indexed_allunique(A)
length(A) < 2 && return true
iter = eachindex(A)
I = iterate(iter)
while I !== nothing
i, s = I
a = A[i]
for j in Iterators.rest(iter, s)
isequal(a, @inbounds A[j]) && return false
end
I = iterate(iter, s)
end
return true
end
function allunique(t::Tuple)
length(t) < 32 || return _hashed_allunique(t)
a = afoldl(true, tail(t)...) do b, x
b & !isequal(first(t), x)
end
return a && allunique(tail(t))
end
allunique(t::Tuple{}) = true
"""
allequal(itr) -> Bool
Return `true` if all values from `itr` are equal when compared with [`isequal`](@ref).
See also: [`unique`](@ref), [`allunique`](@ref).
!!! compat "Julia 1.8"
The `allequal` function requires at least Julia 1.8.
# Examples
```jldoctest
julia> allequal([])
true
julia> allequal([1])
true
julia> allequal([1, 1])
true
julia> allequal([1, 2])
false
julia> allequal(Dict(:a => 1, :b => 1))
false
```
"""
allequal(itr) = isempty(itr) ? true : all(isequal(first(itr)), itr)
allequal(c::Union{AbstractSet,AbstractDict}) = length(c) <= 1
allequal(r::AbstractRange) = iszero(step(r)) || length(r) <= 1
filter!(f, s::Set) = unsafe_filter!(f, s)
const hashs_seed = UInt === UInt64 ? 0x852ada37cfe8e0ce : 0xcfe8e0ce
function hash(s::AbstractSet, h::UInt)
hv = hashs_seed
for x in s
hv ⊻= hash(x)
end
hash(hv, h)
end
convert(::Type{T}, s::T) where {T<:AbstractSet} = s
convert(::Type{T}, s::AbstractSet) where {T<:AbstractSet} = T(s)::T
## replace/replace! ##
function check_count(count::Integer)
count < 0 && throw(DomainError(count, "`count` must not be negative (got $count)"))
return min(count, typemax(Int)) % Int
end
# TODO: use copy!, which is currently unavailable from here since it is defined in Future
_copy_oftype(x, ::Type{T}) where {T} = copyto!(similar(x, T), x)
# TODO: use similar() once deprecation is removed and it preserves keys
_copy_oftype(x::AbstractDict, ::Type{Pair{K,V}}) where {K,V} = merge!(empty(x, K, V), x)
_copy_oftype(x::AbstractSet, ::Type{T}) where {T} = union!(empty(x, T), x)
_copy_oftype(x::AbstractArray{T}, ::Type{T}) where {T} = copy(x)
_copy_oftype(x::AbstractDict{K,V}, ::Type{Pair{K,V}}) where {K,V} = copy(x)
_copy_oftype(x::AbstractSet{T}, ::Type{T}) where {T} = copy(x)
_similar_or_copy(x::Any) = similar(x)
_similar_or_copy(x::Any, ::Type{T}) where {T} = similar(x, T)
# Make a copy on construction since it is faster than inserting elements separately
_similar_or_copy(x::Union{AbstractDict,AbstractSet}) = copy(x)
_similar_or_copy(x::Union{AbstractDict,AbstractSet}, ::Type{T}) where {T} = _copy_oftype(x, T)
# to make replace/replace! work for a new container type Cont, only
# _replace!(new::Callable, res::Cont, A::Cont, count::Int)
# has to be implemented
"""
replace!(A, old_new::Pair...; [count::Integer])
For each pair `old=>new` in `old_new`, replace all occurrences
of `old` in collection `A` by `new`.
Equality is determined using [`isequal`](@ref).
If `count` is specified, then replace at most `count` occurrences in total.
See also [`replace`](@ref replace(A, old_new::Pair...)).
# Examples
```jldoctest
julia> replace!([1, 2, 1, 3], 1=>0, 2=>4, count=2)
4-element Vector{Int64}:
0
4
1
3
julia> replace!(Set([1, 2, 3]), 1=>0)
Set{Int64} with 3 elements:
0
2
3
```
"""
replace!(A, old_new::Pair...; count::Integer=typemax(Int)) =
replace_pairs!(A, A, check_count(count), old_new)
function replace_pairs!(res, A, count::Int, old_new::Tuple{Vararg{Pair}})
@inline function new(x)
for o_n in old_new
isequal(first(o_n), x) && return last(o_n)
end
return x # no replace
end
_replace!(new, res, A, count)
end
"""
replace!(new::Union{Function, Type}, A; [count::Integer])
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 : x, [1, 2, 3, 4])
4-element Vector{Int64}:
2
2
6
4
julia> replace!(Dict(1=>2, 3=>4)) do kv
first(kv) < 3 ? first(kv)=>3 : kv
end
Dict{Int64, Int64} with 2 entries:
3 => 4
1 => 3
julia> replace!(x->2x, Set([3, 6]))
Set{Int64} with 2 elements:
6
12
```
"""
replace!(new::Callable, A; count::Integer=typemax(Int)) =
_replace!(new, A, A, check_count(count))
"""
replace(A, old_new::Pair...; [count::Integer])
Return a copy of collection `A` where, for each pair `old=>new` in `old_new`,
all occurrences of `old` are replaced by `new`.
Equality is determined using [`isequal`](@ref).
If `count` is specified, then replace at most `count` occurrences in total.
The element type of the result is chosen using promotion (see [`promote_type`](@ref))
based on the element type of `A` and on the types of the `new` values in pairs.
If `count` is omitted and the element type of `A` is a `Union`, the element type
of the result will not include singleton types which are replaced with values of
a different type: for example, `Union{T,Missing}` will become `T` if `missing` is
replaced.
See also [`replace!`](@ref), [`splice!`](@ref), [`delete!`](@ref), [`insert!`](@ref).
!!! compat "Julia 1.7"
Version 1.7 is required to replace elements of a `Tuple`.
# Examples
```jldoctest
julia> replace([1, 2, 1, 3], 1=>0, 2=>4, count=2)
4-element Vector{Int64}:
0
4
1
3
julia> replace([1, missing], missing=>0)
2-element Vector{Int64}:
1
0
```
"""
function replace(A, old_new::Pair...; count::Union{Integer,Nothing}=nothing)
V = promote_valuetype(old_new...)
if count isa Nothing
T = promote_type(subtract_singletontype(eltype(A), old_new...), V)
replace_pairs!(_similar_or_copy(A, T), A, typemax(Int), old_new)
else
U = promote_type(eltype(A), V)
replace_pairs!(_similar_or_copy(A, U), A, check_count(count), old_new)
end
end
promote_valuetype(x::Pair{K, V}) where {K, V} = V
promote_valuetype(x::Pair{K, V}, y::Pair...) where {K, V} =
promote_type(V, promote_valuetype(y...))
# Subtract singleton types which are going to be replaced
function subtract_singletontype(::Type{T}, x::Pair{K}) where {T, K}
if issingletontype(K)
typesplit(T, K)
else
T
end
end
subtract_singletontype(::Type{T}, x::Pair{K}, y::Pair...) where {T, K} =
subtract_singletontype(subtract_singletontype(T, y...), x)
"""
replace(new::Union{Function, Type}, A; [count::Integer])
Return a copy of `A` where each value `x` in `A` is replaced by `new(x)`.
If `count` is specified, then replace at most `count` values in total
(replacements being defined as `new(x) !== x`).
!!! compat "Julia 1.7"
Version 1.7 is required to replace elements of a `Tuple`.
# Examples
```jldoctest
julia> replace(x -> isodd(x) ? 2x : x, [1, 2, 3, 4])
4-element Vector{Int64}:
2
2
6
4
julia> replace(Dict(1=>2, 3=>4)) do kv
first(kv) < 3 ? first(kv)=>3 : kv
end
Dict{Int64, Int64} with 2 entries:
3 => 4
1 => 3
```
"""
replace(new::Callable, A; count::Integer=typemax(Int)) =
_replace!(new, _similar_or_copy(A), A, check_count(count))
# Handle ambiguities
replace!(a::Callable, b::Pair; count::Integer=-1) = throw(MethodError(replace!, (a, b)))
replace!(a::Callable, b::Pair, c::Pair; count::Integer=-1) = throw(MethodError(replace!, (a, b, c)))
replace(a::Callable, b::Pair; count::Integer=-1) = throw(MethodError(replace, (a, b)))
replace(a::Callable, b::Pair, c::Pair; count::Integer=-1) = throw(MethodError(replace, (a, b, c)))
### replace! for AbstractDict/AbstractSet
askey(k, ::AbstractDict) = k.first
askey(k, ::AbstractSet) = k
function _replace!(new::Callable, res::Union{AbstractDict,AbstractSet},
A::Union{AbstractDict,AbstractSet}, count::Int)
@assert res isa AbstractDict && A isa AbstractDict ||
res isa AbstractSet && A isa AbstractSet
count == 0 && return res
c = 0
if res === A # cannot replace elements while iterating over A
repl = Pair{eltype(A),eltype(A)}[]
for x in A
y = new(x)
if x !== y
push!(repl, x => y)
c += 1
c == count && break
end
end
for oldnew in repl
pop!(res, askey(first(oldnew), res))
end
for oldnew in repl
push!(res, last(oldnew))
end
else
for x in A
y = new(x)
if x !== y
pop!(res, askey(x, res))
push!(res, y)
c += 1
c == count && break
end
end
end
res
end
### replace! for AbstractArray
function _replace!(new::Callable, res::AbstractArray, A::AbstractArray, count::Int)
c = 0
if count >= length(A) # simpler loop allows for SIMD
if res === A # for optimization only
for i in eachindex(A)
@inbounds Ai = A[i]
y = new(Ai)
@inbounds A[i] = y
end
else
for i in eachindex(A)
@inbounds Ai = A[i]
y = new(Ai)
@inbounds res[i] = y
end
end
else
for i in eachindex(A)
@inbounds Ai = A[i]
if c < count
y = new(Ai)
@inbounds res[i] = y
c += (Ai !== y)
else
@inbounds res[i] = Ai
end
end
end
res
end
### specialization for Dict / Set
function _replace!(new::Callable, t::Dict{K,V}, A::AbstractDict, count::Int) where {K,V}
# we ignore A, which is supposed to be equal to the destination t,
# as it can generally be faster to just replace inline
count == 0 && return t
c = 0
news = Pair{K,V}[]
i = skip_deleted_floor!(t)
@inbounds while i != 0
k1, v1 = t.keys[i], t.vals[i]
x1 = Pair{K,V}(k1, v1)
x2 = new(x1)
if x1 !== x2
k2, v2 = first(x2), last(x2)
if isequal(k1, k2)
t.keys[i] = k2
t.vals[i] = v2
t.age += 1
else
_delete!(t, i)
push!(news, x2)
end
c += 1
c == count && break
end
i = i == typemax(Int) ? 0 : skip_deleted(t, i+1)
end
for n in news
push!(t, n)
end
t
end
function _replace!(new::Callable, t::Set{T}, ::AbstractSet, count::Int) where {T}
_replace!(t.dict, t.dict, count) do kv
k = first(kv)
k2 = new(k)
k2 === k ? kv : k2 => nothing
end
t
end
### replace for tuples
function _replace(f::Callable, t::Tuple, count::Int)
if count == 0 || isempty(t)
t
else
x = f(t[1])
(x, _replace(f, tail(t), count - !==(x, t[1]))...)
end
end
replace(f::Callable, t::Tuple; count::Integer=typemax(Int)) =
_replace(f, t, check_count(count))
function _replace(t::Tuple, count::Int, old_new::Tuple{Vararg{Pair}})
_replace(t, count) do x
@inline
for o_n in old_new
isequal(first(o_n), x) && return last(o_n)
end
return x
end
end
replace(t::Tuple, old_new::Pair...; count::Integer=typemax(Int)) =
_replace(t, check_count(count), old_new)
Computing file changes ...