From 895dc597121ff894a7ae0049b6bb18a99f2be0d5 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Tue, 23 Aug 2022 02:42:03 -0400 Subject: [PATCH 01/16] functional `delete` method for `NamedTuple` and `Tuple`, providing parrallel behavior to `Base.setindex`. --- base/namedtuple.jl | 26 ++++++++++++++++++++++++++ base/tuple.jl | 27 ++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 70ebba34abe87..fd3547c7b46bd 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -379,6 +379,32 @@ function setindex(nt::NamedTuple, v, idx::Symbol) merge(nt, (; idx => v)) end +""" + delete(nt::NamedTuple, key::Symbol) + +Constructs a new `NamedTuple` with the field corresponding to `key` removed. +If no field corresponds to `key`, `nt` is returned. + +```jldoctest +julia> nt = (a = 1, b = 2, c = 3) +(a = 1, b = 2, c = 3) + +julia> Base.delete(nt, :b) +(a = 1, c = 3) + +julia> Base.delete(nt, :z) +(a = 1, b = 2, c = 3) +``` +""" +function delete(nt::NamedTuple{names}, i::Symbol) where {names} + fidx = fieldindex(typeof(nt), i, false) + if fidx === 0 + nt + else + NamedTuple{_delete(fidx, names...)}(_delete(fidx, Tuple(nt)...)) + end +end + """ @NamedTuple{key1::Type1, key2::Type2, ...} @NamedTuple begin key1::Type1; key2::Type2; ...; end diff --git a/base/tuple.jl b/base/tuple.jl index 875d0173c6059..ed4bc54c331d7 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -38,7 +38,7 @@ get(f::Callable, t::Tuple, i::Integer) = i in 1:length(t) ? getindex(t, i) : f() # returns new tuple; N.B.: becomes no-op if `i` is out-of-bounds """ - setindex(c::Tuple, v, i::Integer) + setindex(x::Tuple, v, i::Integer) Creates a new tuple similar to `x` with the value at index `i` set to `v`. Throws a `BoundsError` when out of bounds. @@ -60,6 +60,31 @@ function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}()) end +""" + delete(x::Tuple, i::Integer) + +Creates a new tuple similar to `x` with the value at index `i` removed. +Throws a `BoundsError` when out of bounds. + +# Examples +```jldoctest +julia> Base.delete((1, 2, 3), 3) == (1, 2) +true + +julia> Base.delete((1, 2, 3), 4) +ERROR: BoundsError: attempt to access Tuple{Int64, Int64, Int64} at index [4] +[...] +``` +""" +function delete(x::Tuple, i::Integer) + @boundscheck 1 <= i <= length(x) || throw(BoundsError(x, i)) + _delete(i, x...) +end +function _delete(i::Integer, args::Vararg{Any,N}) where {N} + @inline + return ntuple(j -> ifelse(j < i, args[j], args[j+1]), Val{N-1}()) +end + ## iterating ## From 74613599b295d0871c46d00917f3874ef3d5bbdc Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Tue, 23 Aug 2022 09:23:52 -0400 Subject: [PATCH 02/16] Add tests and finish setindex, insert, fix delete, deleteat --- base/array.jl | 193 +++++++++++++++++++++++++++++++++++++++++++++ base/dict.jl | 66 ++++++++++++++++ base/exports.jl | 3 + base/namedtuple.jl | 3 + base/tuple.jl | 31 +++++--- test/arrayops.jl | 89 +++++++++++++++++++++ test/dict.jl | 34 ++++++++ 7 files changed, 410 insertions(+), 9 deletions(-) diff --git a/base/array.jl b/base/array.jl index 85296b4d94f21..decb5ee2a05d4 100644 --- a/base/array.jl +++ b/base/array.jl @@ -995,6 +995,42 @@ function setindex!(A::Array{T}, X::Array{T}, c::Colon) where T return A end +""" + setindex(collection, value, key...) + +Create a new collection with the element/elements of the location specified by `key` +replaced with `value`(s). + +# Implementation + +`setindex(collection, value, key...)` must have the property such that + +```julia +y1 = setindex(x, value, key...) + +y2 = copy′(x) +@assert convert.(eltype(y2), x) == y2 + +y2[key...] = value +@assert convert.(eltype(y2), y1) == y2 +``` + +with a suitable definition of `copy′` such that `y2[key...] = value` succeeds. + +`setindex` should support more combinations of arguments by widening collection +type as required. +""" +function setindex end + +function setindex(xs::AbstractArray, v, I...) + @_propagate_inbounds_meta + T = promote_type(eltype(xs), typeof(v)) + ys = similar(xs, T) + copy!(ys, xs) + ys[I...] = v + return ys +end + # efficiently grow an array _growbeg!(a::Vector, delta::Integer) = @@ -1133,6 +1169,63 @@ function _append!(a, ::IteratorSize, iter) a end +""" + insert(a::AbstractVector, index::Integer, item) + +Returns a copy of `a` with `item` inserted at `index`. + +See also: [`insert!`](@ref) + +# Examples +```jldoctest +julia> A = Any[1, 2, 3] +3-element Vector{Any}: + 1 + 2 + 3 + +julia> insert(A, 3, "here") == [1, 2, "here", 3] +true + +julia> A == [1, 2, 3] +true +```` +""" +function insert(src::AbstractVector, index, item) + src_index = firstindex(src) + src_end = lastindex(src) + if index == (src_end + 1) + dst = similar(src, promote_type(eltype(src), typeof(item)), length(src) + 1) + for dst_index in eachindex(dst) + @inbounds if isassigned(src, src_index) + @inbounds dst[dst_index] = src[src_index] + end + src_index += 1 + end + @inbounds dst[end] = item + else + @boundscheck (src_end < index || index < src_index) && throw(BoundsError(src, index)) + dst = similar(src, promote_type(eltype(src), typeof(item)), length(src) + 1) + dst_index = firstindex(dst) + while src_index < index + @inbounds if isassigned(src, src_index) + @inbounds dst[dst_index] = src[src_index] + end + dst_index += 1 + src_index += 1 + end + setindex!(dst, item, dst_index) + dst_index += 1 + for i in src_index:src_end + @inbounds if isassigned(src, i) + @inbounds dst[dst_index] = src[i] + end + dst_index += 1 + end + end + dst +end + """ prepend!(a::Vector, collections...) -> collection @@ -1616,6 +1709,106 @@ function deleteat!(a::Vector, inds::AbstractVector{Bool}) return a end +""" + deleteat(a::Vector, i::Integer) + +Remove the item at the given `i` and return the modified `a`. Subsequent items +are shifted to fill the resulting gap. + +See also: [`deleteat`](@ref) + +# Examples +```jldoctest +julia> x = [6, 5, 4] +3-element Vector{Int64}: + 6 + 5 + 4 + +julia> deleteat(x, 2) == [6, 4] +true + +julia> deleteat(x, [2, 3]) == [6] +true + +julia> x == [6, 5, 4] +true +``` +""" +function deleteat(src::AbstractVector, i::Integer) + src_index = firstindex(src) + src_end = lastindex(src) + @boundscheck src_index <= i <= src_end || throw(BoundsError(src, i)) + dst = similar(src, length(src) - 1) + dst_index = firstindex(dst) + while src_index <= src_end + if src_index != i + if @inbounds isassigned(src, src_index) + @inbounds dst[dst_index] = src[src_index] + end + dst_index += 1 + end + src_index += 1 + end + return dst +end +function deleteat(src::AbstractVector, inds::AbstractVector{Bool}) + n = length(src) + @boundscheck length(inds) == n || throw(BoundsError(src, inds)) + dst = similar(src, n) + dst_index = firstindex(dst) + src_index = firstindex(src) + for index in inds + if !index + if @inbounds isassigned(src, src_index) + @inbounds dst[dst_index] = src[src_index] + end + dst_index += 1 + end + src_index += 1 + end + dst +end +function deleteat(src::AbstractVector, inds) + itr = iterate(inds) + if itr === nothing + copy(src) + else + len = length(src) - length(inds) + # `length(inds) > length(a)` then `inds` has repeated or out of bounds indices + len > 0 || throw(BoundsError(src, inds)) + index, state = itr + dst = similar(src, len) + dst_index = firstindex(dst) + src_index = firstindex(src) + src_end = lastindex(src) + src_index > index && throw(BoundsError(src, inds)) + while true + (src_end < index || index < src_index) && throw(BoundsError(src, inds)) + while src_index < index + if @inbounds isassigned(src, src_index) + @inbounds dst[dst_index] = src[src_index] + end + dst_index += 1 + src_index += 1 + end + src_index += 1 + itr = iterate(inds, state) + itr === nothing && break + itr[1] <= index && throw(ArgumentError("indices must be unique and sorted")) + index, state = itr + end + while src_index <= src_end + if @inbounds isassigned(src, src_index) + @inbounds dst[dst_index] = src[src_index] + end + dst_index += 1 + src_index += 1 + end + dst + end +end + const _default_splice = [] """ diff --git a/base/dict.jl b/base/dict.jl index 22fd8a3a9f844..576a952666128 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -395,6 +395,15 @@ function setindex!(h::Dict{K,Any}, v, key::K) where K return h end +function setindex(d0::Dict, v, k) + K = promote_type(keytype(d0), typeof(k)) + V = promote_type(valtype(d0), typeof(v)) + d = Dict{K, V}() + copy!(d, d0) + d[k] = v + return d +end + """ get!(collection, key, default) @@ -669,6 +678,30 @@ function delete!(h::Dict, key) return h end +""" + delete(collection, key) + +Create and return a new collection containing all the elements from `collection` except for +the mapping corresponding to `key`. + +See also: [`delete!`](@ref). + +# Examples +```jldoctest +julia> d = Dict("a"=>1, "b"=>2) +Dict{String,Int64} with 2 entries: + "b" => 2 + "a" => 1 + +julia> delete(d, "b") == Dict("a"=>a) +true + +julia> d == Dict("a"=>1, "b"=>2) +true +``` +""" +delete(collection, k) = delete!(copy(collection), k) + function skip_deleted(h::Dict, i) L = length(h.slots) for i = i:L @@ -821,6 +854,39 @@ function get(default::Callable, dict::ImmutableDict, key) return default() end +function delete(d::ImmutableDict{K,V}, key) where {K,V} + if isdefined(d, :parent) + if isequal(d.key, key) + d.parent + else + ImmutableDict{K,V}(delete(d.parent, key), d.key, d.value) + end + else + d + end +end + +function setindex(d::ImmutableDict, v, k) + if isdefined(d, :parent) + if isequal(d.key, k) + d0 = d.parent + v0 = v + k0 = k + else + d0 = setindex(d.parent, v, k) + v0 = d.value + k0 = d.key + end + else + d0 = d + v0 = v + k0 = k + end + K = promote_type(keytype(d0), typeof(k0)) + V = promote_type(valtype(d0), typeof(v0)) + ImmutableDict{K,V}(d0, k0, v0) +end + # this actually defines reverse iteration (e.g. it should not be used for merge/copy/filter type operations) function iterate(d::ImmutableDict{K,V}, t=d) where {K, V} !isdefined(t, :parent) && return nothing diff --git a/base/exports.jl b/base/exports.jl index f64c3b2913260..c20e7f3e4e42b 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -492,6 +492,7 @@ export # dequeues append!, insert!, + insert, pop!, popat!, prepend!, @@ -512,7 +513,9 @@ export count!, count, delete!, + delete, deleteat!, + deleteat, keepat!, eltype, empty!, diff --git a/base/namedtuple.jl b/base/namedtuple.jl index fd3547c7b46bd..856fbeb0eb25b 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -385,6 +385,9 @@ end Constructs a new `NamedTuple` with the field corresponding to `key` removed. If no field corresponds to `key`, `nt` is returned. +See also: [`delete!`](@ref) + +# Examples ```jldoctest julia> nt = (a = 1, b = 2, c = 3) (a = 1, b = 2, c = 3) diff --git a/base/tuple.jl b/base/tuple.jl index ed4bc54c331d7..28fbaf8cb0018 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -61,31 +61,44 @@ function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} end """ - delete(x::Tuple, i::Integer) + deleteat(x::Tuple, i::Integer) Creates a new tuple similar to `x` with the value at index `i` removed. Throws a `BoundsError` when out of bounds. +See also: [`deleteat!`](@ref) # Examples ```jldoctest -julia> Base.delete((1, 2, 3), 3) == (1, 2) +julia> deleteat((1, 2, 3), 3) == (1, 2) true -julia> Base.delete((1, 2, 3), 4) +julia> deleteat((1, 2, 3), 4) ERROR: BoundsError: attempt to access Tuple{Int64, Int64, Int64} at index [4] [...] ``` """ -function delete(x::Tuple, i::Integer) - @boundscheck 1 <= i <= length(x) || throw(BoundsError(x, i)) - _delete(i, x...) +function deleteat(x::Tuple, i) + @boundscheck checkindex(Bool, eachindex(x), i) || throw(BoundsError(x, i)) + _deleteat(x, i) end -function _delete(i::Integer, args::Vararg{Any,N}) where {N} +function _deleteat(x::Tuple{Vararg{Any,N}}, i::Integer) where {N} @inline - return ntuple(j -> ifelse(j < i, args[j], args[j+1]), Val{N-1}()) + ntuple(j -> j < i ? getfield(x, j) : getfield(x, j + 1), Val{N+1}()) +end + +function insert(x::Tuple{Vararg{Any,N}}, index::Integer, item) where {N} + @boundscheck 1 <= index <= length(x) || throw(BoundsError(x, index)) + ntuple(Val{N+1}()) do j + if j == index + item + elseif j < index + getfield(index, j) + else + getfield(index, j - 1) + end + end end - ## iterating ## function iterate(@nospecialize(t::Tuple), i::Int=1) diff --git a/test/arrayops.jl b/test/arrayops.jl index d4634a393aa86..7e9d29425e07a 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3059,3 +3059,92 @@ end @test c + zero(c) == c end end + +@testset "deleteat" begin + a = [1:10;] + ainds = eachindex(a) + for idx in Any[1, 2, 5, 9, 10, 1:0, 2:1, 1:1, 2:2, 1:2, 2:4, 9:8, 10:9, 9:9, 10:10, + 8:9, 9:10, 6:9, 7:10] + + a_remaining = setdiff(ainds, idx) + # integer indexing with AbstractArray + @test deleteat(a, idx) == a_remaining + + # integer indexing with non-AbstractArray iterable + @test deleteat(a, (i for i in idx)) == a_remaining + + # logical indexing + @test deleteat(a, map(in(idx), 1:length(a))) == a_remaining + end + @test deleteat(a, 11:10) == [1:10;] + @test deleteat(a, [1,3,5,7:10...]) == [2,4,6] + @test_throws BoundsError deleteat(a, 13) + @test_throws BoundsError deleteat(a, [1,13]) + @test_throws ArgumentError deleteat(a, [3,2]) # not sorted + @test_throws BoundsError deleteat(a, 5:20) + @test_throws BoundsError deleteat(a, Bool[]) + @test_throws BoundsError deleteat(a, [true]) + @test_throws BoundsError deleteat(a, falses(11)) + @test_throws BoundsError deleteat(a, [0]) + + @test_throws BoundsError deleteat([], 1) + @test_throws BoundsError deleteat([], [1]) + @test_throws BoundsError deleteat([], [2]) + @test deleteat([], []) == [] + @test deleteat([], Bool[]) == [] + let a = Vector{Any}(undef, 2) + a[1] = 1 + @test isassigned(deleteat(a, [2]), 1) + @test !isassigned(deleteat(a, [1]), 1) + end +end + +==ₜ(_, _) = false +==ₜ(x::T, y::T) where T = x == y + +@testset "setindex" begin + @test @inferred(Base.setindex(Int[1, 2], 3.0, 2)) ==ₜ [1.0, 3.0] + @test @inferred(Base.setindex(Int[1, 2, 3], :two, 2)) ==ₜ [1, :two, 3] + + @testset "no mutation" begin + arr = [1] + @test Base.setindex(arr, 2, 1) ==ₜ [2] + @test arr == [1] + end + + @test @inferred(Base.setindex(Int[1, 2], 3.0, 2)) ==ₜ [1.0, 3.0] + @test @inferred(Base.setindex(Int[1, 2, 3], :two, 2)) ==ₜ [1, :two, 3] + + @testset "no mutation" begin + arr = [1] + @test Base.setindex(arr, 2, 1) ==ₜ [2] + @test arr == [1] + end +end + +@testset "insert" begin + @test @inferred(insert(Int[1, 2], 1, :two)) ==ₜ [:two, 1, 2] + @test @inferred(insert(Int[1, 2], 2, 3.0)) ==ₜ [1.0, 3.0, 2.0] + @test @inferred(insert(Int[1, 2], 3, 3)) ==ₜ [1, 2, 3] + + @test insert(v, 1, "here") == ["here", 1, 2] + @test insert(v, 2, "here") == [1, "here", 2] + @test insert(v, 3, "here") == [1, 2, "here"] + @test_throws BoundsError insert(v, 0, 5) + @test_throws BoundsError insert(v, 0, 5) + + + @test_throws BoundsError insert(v, 0, 5) + for i = 1:4 + vc = copy(v) + @test insert(vc, i, 5) === vc + @test vc == [v[1:(i-1)]; 5; v[i:end]] + end + @test_throws BoundsError insert(v, 5, 5) + + @test insert((1,2), 1, "here") == ("here", 1, 2) + @test insert((1,2), 2, "here") == (1, "here", 2) + @test insert((1,2), 3, "here") == (1, 2, "here") +end + +@test insert(v, i, 5) === insert!([3, 7, 6], i, 5) diff --git a/test/dict.jl b/test/dict.jl index de0ce88fb5a0f..b9094f01e683f 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -1279,3 +1279,37 @@ end sizehint!(d, 10) @test length(d.slots) < 100 end + +@testset "setindex" begin + ==ₜ(_, _) = false + ==ₜ(x::T, y::T) where T = x == y + + @test @inferred(Base.setindex(Dict(:a=>1, :b=>2), 10, :a)) ==ₜ + Dict(:a=>10, :b=>2) + @test @inferred(Base.setindex(Dict(:a=>1, :b=>2), 3, "c")) ==ₜ + Dict(:a=>1, :b=>2, "c"=>3) + @test @inferred(Base.setindex(Dict(:a=>1, :b=>2), 3.0, :c)) ==ₜ + Dict(:a=>1.0, :b=>2.0, :c=>3.0) + + @testset "no mutation" begin + dict = Dict(:a=>1, :b=>2) + @test Base.setindex(dict, 10, :a) ==ₜ Dict(:a=>10, :b=>2) + @test dict == Dict(:a=>1, :b=>2) + end + + d1 = ImmutableDict{Symbol,Int}() + d2 = ImmutableDict(d1, :a => 1) + @test Base.setindex(d1, 1, :a) == d2 + @test Base.setindex(d2, 2, :b) == ImmutableDict(d2, :b => 2) +end + +@testset "delete" begin + @test delete(Dict(:a=>1.0, :b=>2.0, :c=>3.0), :b) == Dict(:a=>1.0, :c=>3.0) + + d1 = ImmutableDict{Symbol,Int}() + d2 = ImmutableDict(d1, :a => 1) + d3 = ImmutableDict(d2, :b => 2) + @test delete(d2, :a) == d1 + @test delete(d2, :c) == d2 + @test delete(d3, :a) == ImmutableDict(d1, :b => 2) +end From 94eb379198dfed11948abcf1d4656162641af80e Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 24 Aug 2022 08:34:36 -0400 Subject: [PATCH 03/16] fix logical index deletions --- base/array.jl | 102 ++++++++++++++++++++++------------------------- test/arrayops.jl | 14 +++---- test/tuple.jl | 6 +++ 3 files changed, 58 insertions(+), 64 deletions(-) diff --git a/base/array.jl b/base/array.jl index decb5ee2a05d4..e669caf2b9107 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1192,35 +1192,35 @@ true ```` """ function insert(src::AbstractVector, index, item) - src_index = firstindex(src) + src_idx = firstindex(src) src_end = lastindex(src) if index == (src_end + 1) dst = similar(src, promote_type(eltype(src), typeof(item)), length(src) + 1) - for dst_index in eachindex(dst) - @inbounds if isassigned(src, src_index) - @inbounds dst[dst_index] = src[src_index] + for dst_idx in eachindex(dst) + @inbounds if isassigned(src, src_idx) + @inbounds dst[dst_idx] = src[src_idx] end - src_index += 1 + src_idx += 1 end @inbounds dst[end] = item else - @boundscheck (src_end < index || index < src_index) && throw(BoundsError(src, index)) + @boundscheck (src_end < index || index < src_idx) && throw(BoundsError(src, index)) dst = similar(src, promote_type(eltype(src), typeof(item)), length(src) + 1) - dst_index = firstindex(dst) - while src_index < index - @inbounds if isassigned(src, src_index) - @inbounds dst[dst_index] = src[src_index] + dst_idx = firstindex(dst) + while src_idx < index + @inbounds if isassigned(src, src_idx) + @inbounds dst[dst_idx] = src[src_idx] end - dst_index += 1 - src_index += 1 + dst_idx += 1 + src_idx += 1 end - setindex!(dst, item, dst_index) - dst_index += 1 - for i in src_index:src_end + setindex!(dst, item, dst_idx) + dst_idx += 1 + for i in src_idx:src_end @inbounds if isassigned(src, i) - @inbounds dst[dst_index] = src[i] + @inbounds dst[dst_idx] = src[i] end - dst_index += 1 + dst_idx += 1 end end dst @@ -1736,36 +1736,32 @@ true ``` """ function deleteat(src::AbstractVector, i::Integer) - src_index = firstindex(src) + src_idx = firstindex(src) src_end = lastindex(src) - @boundscheck src_index <= i <= src_end || throw(BoundsError(src, i)) + src_idx <= i <= src_end || throw(BoundsError(src, i)) dst = similar(src, length(src) - 1) - dst_index = firstindex(dst) - while src_index <= src_end - if src_index != i - if @inbounds isassigned(src, src_index) - @inbounds dst[dst_index] = src[src_index] - end - dst_index += 1 + dst_idx = firstindex(dst) + while src_idx <= src_end + if src_idx != i + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 end - src_index += 1 + src_idx += 1 end - return dst + dst end function deleteat(src::AbstractVector, inds::AbstractVector{Bool}) n = length(src) - @boundscheck length(inds) == n || throw(BoundsError(src, inds)) - dst = similar(src, n) - dst_index = firstindex(dst) - src_index = firstindex(src) + length(inds) == n || throw(BoundsError(src, inds)) + dst = similar(src, n - count(inds)) + dst_idx = firstindex(dst) + src_idx = firstindex(src) for index in inds if !index - if @inbounds isassigned(src, src_index) - @inbounds dst[dst_index] = src[src_index] - end - dst_index += 1 + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 end - src_index += 1 + src_idx += 1 end dst end @@ -1775,35 +1771,31 @@ function deleteat(src::AbstractVector, inds) copy(src) else len = length(src) - length(inds) - # `length(inds) > length(a)` then `inds` has repeated or out of bounds indices + # `length(inds) > length(src)` then `inds` has repeated or out of bounds indices len > 0 || throw(BoundsError(src, inds)) index, state = itr dst = similar(src, len) - dst_index = firstindex(dst) - src_index = firstindex(src) + dst_idx = firstindex(dst) + src_idx = firstindex(src) src_end = lastindex(src) - src_index > index && throw(BoundsError(src, inds)) + src_idx > index && throw(BoundsError(src, inds)) while true - (src_end < index || index < src_index) && throw(BoundsError(src, inds)) - while src_index < index - if @inbounds isassigned(src, src_index) - @inbounds dst[dst_index] = src[src_index] - end - dst_index += 1 - src_index += 1 + (src_end < index || index < src_idx) && throw(BoundsError(src, inds)) + while src_idx < index + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 + src_idx += 1 end - src_index += 1 + src_idx += 1 itr = iterate(inds, state) itr === nothing && break itr[1] <= index && throw(ArgumentError("indices must be unique and sorted")) index, state = itr end - while src_index <= src_end - if @inbounds isassigned(src, src_index) - @inbounds dst[dst_index] = src[src_index] - end - dst_index += 1 - src_index += 1 + while src_idx <= src_end + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 + src_idx += 1 end dst end diff --git a/test/arrayops.jl b/test/arrayops.jl index 7e9d29425e07a..286130745cd56 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3074,7 +3074,7 @@ end @test deleteat(a, (i for i in idx)) == a_remaining # logical indexing - @test deleteat(a, map(in(idx), 1:length(a))) == a_remaining + @test deleteat(a, map(in(idx), ainds)) == a_remaining end @test deleteat(a, 11:10) == [1:10;] @test deleteat(a, [1,3,5,7:10...]) == [2,4,6] @@ -3123,9 +3123,10 @@ end end @testset "insert" begin - @test @inferred(insert(Int[1, 2], 1, :two)) ==ₜ [:two, 1, 2] - @test @inferred(insert(Int[1, 2], 2, 3.0)) ==ₜ [1.0, 3.0, 2.0] - @test @inferred(insert(Int[1, 2], 3, 3)) ==ₜ [1, 2, 3] + v = Int[1, 2] + @test @inferred(insert(v, 1, :two)) ==ₜ [:two, 1, 2] + @test @inferred(insert(v, 2, 3.0)) ==ₜ [1.0, 3.0, 2.0] + @test @inferred(insert(v, 3, 3)) ==ₜ [1, 2, 3] @test insert(v, 1, "here") == ["here", 1, 2] @test insert(v, 2, "here") == [1, "here", 2] @@ -3133,7 +3134,6 @@ end @test_throws BoundsError insert(v, 0, 5) @test_throws BoundsError insert(v, 0, 5) - @test_throws BoundsError insert(v, 0, 5) for i = 1:4 vc = copy(v) @@ -3141,10 +3141,6 @@ end @test vc == [v[1:(i-1)]; 5; v[i:end]] end @test_throws BoundsError insert(v, 5, 5) - - @test insert((1,2), 1, "here") == ("here", 1, 2) - @test insert((1,2), 2, "here") == (1, "here", 2) - @test insert((1,2), 3, "here") == (1, 2, "here") end @test insert(v, i, 5) === insert!([3, 7, 6], i, 5) diff --git a/test/tuple.jl b/test/tuple.jl index 39491a249f696..d2bf8987d3802 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -757,3 +757,9 @@ g42457(a, b) = Base.isequal(a, b) ? 1 : 2.0 # issue #46049: setindex(::Tuple) regression @inferred Base.setindex((1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), 42, 1) + +@testset "insert" begin + @test insert((1,2), 1, "here") == ("here", 1, 2) + @test insert((1,2), 2, "here") == (1, "here", 2) + @test insert((1,2), 3, "here") == (1, 2, "here") +end From 495ab02789c346b61ec9268799d17590185e94f2 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 24 Aug 2022 10:09:21 -0400 Subject: [PATCH 04/16] fix insert tests --- base/tuple.jl | 6 +++--- test/arrayops.jl | 10 ---------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/base/tuple.jl b/base/tuple.jl index 28fbaf8cb0018..9e202bfbfbe78 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -87,14 +87,14 @@ function _deleteat(x::Tuple{Vararg{Any,N}}, i::Integer) where {N} end function insert(x::Tuple{Vararg{Any,N}}, index::Integer, item) where {N} - @boundscheck 1 <= index <= length(x) || throw(BoundsError(x, index)) + @boundscheck 1 <= index <= (length(x) + 1) || throw(BoundsError(x, index)) ntuple(Val{N+1}()) do j if j == index item elseif j < index - getfield(index, j) + getfield(x, j) else - getfield(index, j - 1) + getfield(x, j - 1) end end end diff --git a/test/arrayops.jl b/test/arrayops.jl index 286130745cd56..b5f42ba53ba54 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3133,14 +3133,4 @@ end @test insert(v, 3, "here") == [1, 2, "here"] @test_throws BoundsError insert(v, 0, 5) @test_throws BoundsError insert(v, 0, 5) - - @test_throws BoundsError insert(v, 0, 5) - for i = 1:4 - vc = copy(v) - @test insert(vc, i, 5) === vc - @test vc == [v[1:(i-1)]; 5; v[i:end]] - end - @test_throws BoundsError insert(v, 5, 5) end - -@test insert(v, i, 5) === insert!([3, 7, 6], i, 5) From 44eba7e19a4e5705e6af0f4d25fc8b09abaee31e Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Fri, 26 Aug 2022 11:04:17 -0400 Subject: [PATCH 05/16] More support for non-scalar `setindex` --- base/array.jl | 34 ++++++++++++++++++++++++++++------ base/namedtuple.jl | 5 +++-- base/tuple.jl | 1 - test/arrayops.jl | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/namedtuple.jl | 3 +++ test/tuple.jl | 7 +++++++ 6 files changed, 85 insertions(+), 9 deletions(-) diff --git a/base/array.jl b/base/array.jl index e669caf2b9107..20953d3079e91 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1022,14 +1022,36 @@ type as required. """ function setindex end -function setindex(xs::AbstractArray, v, I...) +function setindex(x::AbstractArray, v, i...) @_propagate_inbounds_meta - T = promote_type(eltype(xs), typeof(v)) - ys = similar(xs, T) - copy!(ys, xs) - ys[I...] = v - return ys + inds = to_indices(x, i) + if inds isa Tuple{Vararg{Integer}} + T = promote_type(eltype(x), typeof(v)) + else + T = promote_type(eltype(x), eltype(v)) + end + y = similar(x, T) + copy!(y, x) + y[inds...] = v + return y +end +function setindex(t::Tuple, v, inds::AbstractUnitRange{<:Integer}) + start = first(inds) + stop = last(inds) + offset = start - firstindex(v) + ntuple(Val{nfields(t)}()) do i + if i < start || i > stop + getfield(t, i) + else + v[i - offset] + end + end end +function setindex(t::Tuple, v, inds::AbstractVector{<:Integer}) + @_propagate_inbounds_meta + (setindex!(Any[t...], v, inds)...,) +end + # efficiently grow an array diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 856fbeb0eb25b..10772833cd6db 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -375,8 +375,9 @@ julia> Base.setindex(nt, "a", :a) (a = "a",) ``` """ -function setindex(nt::NamedTuple, v, idx::Symbol) - merge(nt, (; idx => v)) +setindex(nt::NamedTuple, v, idx::Symbol) = merge(nt, (; idx => v)) +function setindex(nt::NamedTuple, vs, idxs::AbstractVector{Symbol}) + merge(nt, (; [i => v for (i,v) in zip(idxs, vs)]...)) end """ diff --git a/base/tuple.jl b/base/tuple.jl index 9e202bfbfbe78..796582974bced 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -54,7 +54,6 @@ function setindex(x::Tuple, v, i::Integer) @inline _setindex(v, i, x...) end - function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} @inline return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}()) diff --git a/test/arrayops.jl b/test/arrayops.jl index b5f42ba53ba54..8efd0824b512c 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3120,6 +3120,50 @@ end @test Base.setindex(arr, 2, 1) ==ₜ [2] @test arr == [1] end + + @testset "$(typeof(x))" for x in [ + zeros(3), + falses(3), + spzeros(3), + ] + y = Base.setindex(x, true, 1) + @test iszero(x) # x is not mutated + @test y[1] == true + @test iszero(x[CartesianIndices(size(x)) .== [CartesianIndex(1)]]) + + y2 = Base.setindex(x, one.(x), :) + @test iszero(x) + @test all(isone, y2) + end + + @testset "$(typeof(x))" for x in [ + zeros(3, 3), + falses(3, 3), + spzeros(3, 3), + ] + y = Base.setindex(x, true, 1, 1) + @test iszero(x) # x is not mutated + @test y[1, 1] == true + @test iszero(x[CartesianIndices(size(x)) .== [CartesianIndex(1, 1)]]) + + y2 = Base.setindex(x, one.(x), :, :) + @test iszero(x) + @test all(isone, y2) + end + + @testset "$(typeof(x))" for x in [ + zeros(3, 3, 3), + falses(3, 3, 3), + ] + y = Base.setindex(x, true, 1, 1, 1) + @test iszero(x) # x is not mutated + @test y[1, 1, 1] == true + @test iszero(x[CartesianIndices(size(x)) .== [CartesianIndex(1, 1, 1)]]) + + y2 = Base.setindex(x, one.(x), :, :, :) + @test iszero(x) + @test all(isone, y2) + end end @testset "insert" begin diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 6c41ab788b733..ca28054db8fd3 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -305,6 +305,9 @@ let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v) @test Base.setindex(nt1, "value", :a) == (a="value",) @test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}} end +@test setindex((a = 3, b = 1), (1, 2, 3), [:a, :b, :c]) === (a = 1, b = 2, c = 3) + + # @NamedTuple @testset "@NamedTuple" begin diff --git a/test/tuple.jl b/test/tuple.jl index d2bf8987d3802..0f721232455fa 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -642,6 +642,13 @@ end f() = Base.setindex((1:1, 2:2, 3:3), 9, 1) @test @inferred(f()) == (9, 2:2, 3:3) + + @test setindex((1,), [2], 1:1) === (2, ) + @test setindex((1, 2, 3, 4), [3, 2], 2:3) === (1, 3, 2, 4) + @test setindex((1, 2, 3, 4), [4, 3, 2, 1], 1:4) === (4, 3, 2, 1) + @test setindex((1,), [2], [1]) === (2, ) + @test setindex((1, 2, 3, 4), [3, 2], [2,3]) === (1, 3, 2, 4) + @test setindex((1, 2, 3, 4), [4, 3, 2, 1], [1,2,3,4]) === (4, 3, 2, 1) end @testset "inferrable range indexing with constant values" begin From c05019b6fa96d4827efce0a756213dc81f9a8012 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Fri, 26 Aug 2022 12:19:30 -0400 Subject: [PATCH 06/16] Remove spzero dependent tests and add `Base.` for `setindex` --- test/arrayops.jl | 2 -- test/namedtuple.jl | 2 +- test/tuple.jl | 12 ++++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/test/arrayops.jl b/test/arrayops.jl index 8efd0824b512c..70e73ad9a1bbf 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3124,7 +3124,6 @@ end @testset "$(typeof(x))" for x in [ zeros(3), falses(3), - spzeros(3), ] y = Base.setindex(x, true, 1) @test iszero(x) # x is not mutated @@ -3139,7 +3138,6 @@ end @testset "$(typeof(x))" for x in [ zeros(3, 3), falses(3, 3), - spzeros(3, 3), ] y = Base.setindex(x, true, 1, 1) @test iszero(x) # x is not mutated diff --git a/test/namedtuple.jl b/test/namedtuple.jl index ca28054db8fd3..6f4eb70efc868 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -305,7 +305,7 @@ let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v) @test Base.setindex(nt1, "value", :a) == (a="value",) @test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}} end -@test setindex((a = 3, b = 1), (1, 2, 3), [:a, :b, :c]) === (a = 1, b = 2, c = 3) +@test Base.setindex((a = 3, b = 1), (1, 2, 3), [:a, :b, :c]) === (a = 1, b = 2, c = 3) diff --git a/test/tuple.jl b/test/tuple.jl index 0f721232455fa..df0883ba45877 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -643,12 +643,12 @@ end f() = Base.setindex((1:1, 2:2, 3:3), 9, 1) @test @inferred(f()) == (9, 2:2, 3:3) - @test setindex((1,), [2], 1:1) === (2, ) - @test setindex((1, 2, 3, 4), [3, 2], 2:3) === (1, 3, 2, 4) - @test setindex((1, 2, 3, 4), [4, 3, 2, 1], 1:4) === (4, 3, 2, 1) - @test setindex((1,), [2], [1]) === (2, ) - @test setindex((1, 2, 3, 4), [3, 2], [2,3]) === (1, 3, 2, 4) - @test setindex((1, 2, 3, 4), [4, 3, 2, 1], [1,2,3,4]) === (4, 3, 2, 1) + @test Base.setindex((1,), [2], 1:1) === (2, ) + @test Base.setindex((1, 2, 3, 4), [3, 2], 2:3) === (1, 3, 2, 4) + @test Base.setindex((1, 2, 3, 4), [4, 3, 2, 1], 1:4) === (4, 3, 2, 1) + @test Base.setindex((1,), [2], [1]) === (2, ) + @test Base.setindex((1, 2, 3, 4), [3, 2], [2,3]) === (1, 3, 2, 4) + @test Base.setindex((1, 2, 3, 4), [4, 3, 2, 1], [1,2,3,4]) === (4, 3, 2, 1) end @testset "inferrable range indexing with constant values" begin From 82af2535e8e6695cf1386749b3d97fbf7eeec21c Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 27 Aug 2022 16:49:00 -0400 Subject: [PATCH 07/16] fix doc tests --- base/dict.jl | 7 ++----- base/namedtuple.jl | 2 +- base/tuple.jl | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/base/dict.jl b/base/dict.jl index 576a952666128..47cb3345daffc 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -688,12 +688,9 @@ See also: [`delete!`](@ref). # Examples ```jldoctest -julia> d = Dict("a"=>1, "b"=>2) -Dict{String,Int64} with 2 entries: - "b" => 2 - "a" => 1 +julia> d = Dict("a"=>1, "b"=>2); -julia> delete(d, "b") == Dict("a"=>a) +julia> delete(d, "b") == Dict("a"=>1) true julia> d == Dict("a"=>1, "b"=>2) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 10772833cd6db..8d2cad618ecf7 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -405,7 +405,7 @@ function delete(nt::NamedTuple{names}, i::Symbol) where {names} if fidx === 0 nt else - NamedTuple{_delete(fidx, names...)}(_delete(fidx, Tuple(nt)...)) + NamedTuple{_deleteat(fidx, names...)}(_deleteat(fidx, Tuple(nt)...)) end end diff --git a/base/tuple.jl b/base/tuple.jl index 796582974bced..075b0318910b5 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -82,7 +82,7 @@ function deleteat(x::Tuple, i) end function _deleteat(x::Tuple{Vararg{Any,N}}, i::Integer) where {N} @inline - ntuple(j -> j < i ? getfield(x, j) : getfield(x, j + 1), Val{N+1}()) + ntuple(j -> j < i ? getfield(x, j) : getfield(x, j + 1), Val{N-1}()) end function insert(x::Tuple{Vararg{Any,N}}, index::Integer, item) where {N} From d7b0673a37d1204dd02eba4f410b1934b08ff859 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 28 Aug 2022 12:07:32 -0400 Subject: [PATCH 08/16] add refs to doc strings --- base/array.jl | 4 ++-- base/namedtuple.jl | 22 +++++++++++++--------- base/tuple.jl | 3 ++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/base/array.jl b/base/array.jl index 20953d3079e91..fc8c6013757b9 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1196,7 +1196,7 @@ end Returns a copy of `a` with `item` inserted at `index`. -See also: [`insert!`](@ref) +See also: [`insert!`](@ref), [`deleteat`](@ref), [`delete`](@ref) # Examples ```jldoctest @@ -1737,7 +1737,7 @@ end Remove the item at the given `i` and return the modified `a`. Subsequent items are shifted to fill the resulting gap. -See also: [`deleteat`](@ref) +See also: [`deleteat!`](@ref), [`delete`](@ref), [`insert`](@ref) # Examples ```jldoctest diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 8d2cad618ecf7..5c651546b7a33 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -382,31 +382,35 @@ end """ delete(nt::NamedTuple, key::Symbol) + delete(nt::NamedTuple, key::Integer) Constructs a new `NamedTuple` with the field corresponding to `key` removed. If no field corresponds to `key`, `nt` is returned. -See also: [`delete!`](@ref) +See also: [`delete!`](@ref), [`deleteat`](@ref) # Examples ```jldoctest julia> nt = (a = 1, b = 2, c = 3) (a = 1, b = 2, c = 3) -julia> Base.delete(nt, :b) +julia> delete(nt, :b) (a = 1, c = 3) -julia> Base.delete(nt, :z) +julia> delete(nt, 2) +(a = 1, c = 3) + +julia> delete(nt, :z) (a = 1, b = 2, c = 3) ``` """ -function delete(nt::NamedTuple{names}, i::Symbol) where {names} +function delete(nt::NamedTuple, i::Symbol) fidx = fieldindex(typeof(nt), i, false) - if fidx === 0 - nt - else - NamedTuple{_deleteat(fidx, names...)}(_deleteat(fidx, Tuple(nt)...)) - end + fidx === 0 ? nt : unsafe_delete(nt, fidx) +end +delete(nt::NamedTuple, i::Integer) = (i < 1 || i > length(nt)) ? nt : unsafe_delete(nt, i) +function unsafe_delete(nt::NamedTuple{names}, i::Integer) where {names} + NamedTuple{_deleteat(names, i)}(_deleteat(Tuple(nt), i)) end """ diff --git a/base/tuple.jl b/base/tuple.jl index 075b0318910b5..4748068ff2894 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -64,7 +64,8 @@ end Creates a new tuple similar to `x` with the value at index `i` removed. Throws a `BoundsError` when out of bounds. -See also: [`deleteat!`](@ref) + +See also: [`deleteat!`](@ref), [`delete`](@ref) # Examples ```jldoctest From 230e352741521c04264638443437ba347cb962ea Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 3 Sep 2022 00:57:52 -0400 Subject: [PATCH 09/16] ensure indices and values in `setindex` are same length --- base/namedtuple.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 5c651546b7a33..3d7123fc28ce8 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -377,6 +377,7 @@ julia> Base.setindex(nt, "a", :a) """ setindex(nt::NamedTuple, v, idx::Symbol) = merge(nt, (; idx => v)) function setindex(nt::NamedTuple, vs, idxs::AbstractVector{Symbol}) + length(vs) == length(idxs) || throw_setindex_mismatch(v, idxs) merge(nt, (; [i => v for (i,v) in zip(idxs, vs)]...)) end From ee924a1cf6afbf53c256f9d9b8945df3808b0b33 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 3 Sep 2022 03:45:16 -0400 Subject: [PATCH 10/16] specify `index` type for `insert` --- base/array.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/array.jl b/base/array.jl index fc8c6013757b9..3be4f9f9f1cd5 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1213,7 +1213,7 @@ julia> A == [1, 2, 3] true ```` """ -function insert(src::AbstractVector, index, item) +function insert(src::AbstractVector, index::Integer, item) src_idx = firstindex(src) src_end = lastindex(src) if index == (src_end + 1) From f7e9888624139548c07de4468af0f16f7e78baa9 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 3 Sep 2022 03:45:45 -0400 Subject: [PATCH 11/16] iterator `deleteat` for tuple --- base/tuple.jl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/base/tuple.jl b/base/tuple.jl index 4748068ff2894..0bfa9be8d35f3 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -77,7 +77,7 @@ ERROR: BoundsError: attempt to access Tuple{Int64, Int64, Int64} at index [4] [...] ``` """ -function deleteat(x::Tuple, i) +function deleteat(x::Tuple, i::Integer) @boundscheck checkindex(Bool, eachindex(x), i) || throw(BoundsError(x, i)) _deleteat(x, i) end @@ -86,6 +86,22 @@ function _deleteat(x::Tuple{Vararg{Any,N}}, i::Integer) where {N} ntuple(j -> j < i ? getfield(x, j) : getfield(x, j + 1), Val{N-1}()) end +""" + deleteat(t::Tuple, inds) + +Return a new tuple without the itmes at the indices given by `inds`. + +# Examples + +```jldoctest +julia> x = (6, 5, 4); + +julia> deleteat(x, [2, 3]) +(6,) +``` +""" +deleteat(t::Tuple, inds) = (deleteat!(Any[t...], inds)...,) + function insert(x::Tuple{Vararg{Any,N}}, index::Integer, item) where {N} @boundscheck 1 <= index <= (length(x) + 1) || throw(BoundsError(x, index)) ntuple(Val{N+1}()) do j From 2e4012116e32f91c7e579956651a63a686edaf03 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 21 Sep 2022 21:13:37 -0400 Subject: [PATCH 12/16] specialize deleteat on unit ranges --- base/array.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/base/array.jl b/base/array.jl index 3be4f9f9f1cd5..d21247ce14123 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1787,6 +1787,25 @@ function deleteat(src::AbstractVector, inds::AbstractVector{Bool}) end dst end +function deleteat(src::AbstractVector, inds::AbstractUnitRange{<:Integer}) + srcinds = eachindex(src) + N = length(srcinds) + dst = similar(src, N - length(inds)) + src_idx = first(srcinds) + dst_idx = firstindex(dst) + while src_idx < first(inds) + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + src_idx += 1 + dst_idx += 1 + end + src_idx = last(inds) + 1 + while src_idx <= N + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + src_idx += 1 + dst_idx += 1 + end + dst +end function deleteat(src::AbstractVector, inds) itr = iterate(inds) if itr === nothing From 94191a84d1665d6f2c4f77176ad3f58dbd4dd496 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 25 Sep 2022 03:53:59 -0400 Subject: [PATCH 13/16] Support `setindex` for `AbstractDict` (not just `Dict`) --- base/abstractdict.jl | 16 ++++++++++++++++ base/dict.jl | 10 ---------- test/dict.jl | 4 ++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 7f1d8b4a1c504..dd80101e127b0 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -548,6 +548,22 @@ end # that we need to avoid dispatch loops if setindex!(t,v,k) is not defined.) getindex(t::AbstractDict, k1, k2, ks...) = getindex(t, tuple(k1,k2,ks...)) setindex!(t::AbstractDict, v, k1, k2, ks...) = setindex!(t, v, tuple(k1,k2,ks...)) +function setindex(src::AbstractDict{K,V}, val::V, key::K) where {K,V} + dst = copy(src) + dst[key] = val + return dst +end +function setindex(src::AbstractDict{K,V}, val, key) where {K,V} + dst = empty(src, promote_type(K,typeof(key)), promote_type(V,typeof(val))) + if haslength(src) + sizehint!(dst, length(src)) + end + for (k,v) in src + dst[k] = v + end + dst[key] = val + return dst +end get!(t::AbstractDict, key, default) = get!(() -> default, t, key) function get!(default::Callable, t::AbstractDict{K,V}, key) where K where V diff --git a/base/dict.jl b/base/dict.jl index 47cb3345daffc..4407ba599be6d 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -395,16 +395,6 @@ function setindex!(h::Dict{K,Any}, v, key::K) where K return h end -function setindex(d0::Dict, v, k) - K = promote_type(keytype(d0), typeof(k)) - V = promote_type(valtype(d0), typeof(v)) - d = Dict{K, V}() - copy!(d, d0) - d[k] = v - return d -end - - """ get!(collection, key, default) diff --git a/test/dict.jl b/test/dict.jl index b9094f01e683f..a0ba4b044cefb 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -1295,6 +1295,10 @@ end dict = Dict(:a=>1, :b=>2) @test Base.setindex(dict, 10, :a) ==ₜ Dict(:a=>10, :b=>2) @test dict == Dict(:a=>1, :b=>2) + + iddict = IdDict{Any,String}(true => "yes", 1 => "no", 1.0 => "maybe") + @test Base.setindex(iddict, :yes, true) ==ₜ IdDict{Any,Any}(true => :yes, 1 => "no", 1.0 => "maybe") + @test iddict == iddict end d1 = ImmutableDict{Symbol,Int}() From b5df3c7473ee45c56742b6b137579836030c63b7 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Tue, 27 Sep 2022 02:30:22 -0400 Subject: [PATCH 14/16] add to news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 421093b6e0681..26b02d3e565aa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -71,6 +71,7 @@ New library functions * New function `stack(x)` which generalises `reduce(hcat, x::Vector{<:Vector})` to any dimensionality, and allows any iterators of iterators. Method `stack(f, x)` generalises `mapreduce(f, hcat, x)` and is efficient. ([#43334]) +* New functions `delete`, `deleteat`, and `insert` provide non-mutating counterparts to `delete!`, `deleteat!`, and `insert!` ([#46453]). Library changes --------------- @@ -85,6 +86,7 @@ Library changes * `@time` now separates out % time spent recompiling invalidated methods ([#45015]). * `eachslice` now works over multiple dimensions; `eachslice`, `eachrow` and `eachcol` return a `Slices` object, which allows dispatching to provide more efficient methods ([#32310]). +* The non-mutationg `Base.setindex` function now has `AbstractDict` support ([#46453]). Standard library changes ------------------------ From abb003edba2009d5119bc28da166db28c660c762 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Thu, 10 Nov 2022 01:18:41 -0500 Subject: [PATCH 15/16] Add performance note to `setindex` and `delete` --- base/array.jl | 3 +++ base/dict.jl | 3 +++ 2 files changed, 6 insertions(+) diff --git a/base/array.jl b/base/array.jl index 30e991a17e639..f254f978d352c 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1013,6 +1013,9 @@ end Create a new collection with the element/elements of the location specified by `key` replaced with `value`(s). +Note that this may be particularly expensive when `collection` is large and whose storage +cannot be shared between seperate instances. + # Implementation `setindex(collection, value, key...)` must have the property such that diff --git a/base/dict.jl b/base/dict.jl index 05e6113ea5252..9cf5292cd2d5a 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -674,6 +674,9 @@ end Create and return a new collection containing all the elements from `collection` except for the mapping corresponding to `key`. +Note that this may be particularly expensive when `collection` is large and whose storage +cannot be shared between seperate instances. + See also: [`delete!`](@ref). # Examples From cd5a701d0047e50536ad942e17e6ab1d271eac8d Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Thu, 2 Mar 2023 12:49:31 -0500 Subject: [PATCH 16/16] Throw appropriate errors for `setindex(::Tuple, ...)` --- base/accumulate.jl | 7 +++++++ base/array.jl | 7 ++----- test/arrayops.jl | 3 +++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/base/accumulate.jl b/base/accumulate.jl index eeb9759e125c7..a159f1d389412 100644 --- a/base/accumulate.jl +++ b/base/accumulate.jl @@ -434,3 +434,10 @@ function _accumulate1!(op, B, v1, A::AbstractVector, dim::Integer) end return B end + +function setindex(t::Tuple, v, inds::AbstractVector{<:Integer}) + Base.@_propagate_inbounds_meta + foldl(ntuple(identity, length(inds)); init=t) do acc, i + Base.setindex(acc, v[i], inds[i]) + end +end diff --git a/base/array.jl b/base/array.jl index f254f978d352c..8f292ceb949fa 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1051,6 +1051,8 @@ function setindex(x::AbstractArray, v, i...) return y end function setindex(t::Tuple, v, inds::AbstractUnitRange{<:Integer}) + @boundscheck checkindex(Bool, eachindex(t), inds) || throw(BoundsError(t, inds)) + @boundscheck setindex_shape_check(v, length(inds)) start = first(inds) stop = last(inds) offset = start - firstindex(v) @@ -1062,11 +1064,6 @@ function setindex(t::Tuple, v, inds::AbstractUnitRange{<:Integer}) end end end -function setindex(t::Tuple, v, inds::AbstractVector{<:Integer}) - @_propagate_inbounds_meta - (setindex!(Any[t...], v, inds)...,) -end - # efficiently grow an array diff --git a/test/arrayops.jl b/test/arrayops.jl index ebf7f04a3b8ed..b846e74627a83 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3178,6 +3178,9 @@ end @test iszero(x) @test all(isone, y2) end + + @test_throws DimensionMismatch Base.setindex((1, 2, 3), [10, 11], 3:3) + @test_throws BoundsError Base.setindex((1, 2, 3), [10, 11], 3:4) end @testset "insert" begin