From 2e455e986aa23b7beb5bfbe9629c120b3490feee Mon Sep 17 00:00:00 2001 From: Andy Ferris Date: Mon, 9 Oct 2017 07:24:00 +1000 Subject: [PATCH] Added `empty * `empty` returns an `Associative` with no keys, replacing `similar`. * `similar` now ensures the `Associative`'s keys are initialized, and matches more closely the behavior of `similar(::AbstractArray)`. --- NEWS.md | 7 ++++++ base/abstractarray.jl | 22 ++++++++++++++++-- base/associative.jl | 40 +++++++++++++++++++++++++++++--- base/deepcopy.jl | 2 +- base/deprecated.jl | 4 ++++ base/dict.jl | 35 +++++++++++++++++++--------- base/env.jl | 2 -- base/exports.jl | 1 + base/pkg/query.jl | 4 ++-- base/tuple.jl | 7 ++++++ base/weakkeydict.jl | 3 +-- test/abstractarray.jl | 14 ++++++++++- test/dict.jl | 54 +++++++++++++++++++++++++++++++++++++++---- test/tuple.jl | 2 ++ 14 files changed, 168 insertions(+), 29 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1126cb2d11796..3cb9d008493fc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1246,6 +1246,13 @@ Deprecated or removed * `EnvHash` has been renamed to `EnvDict` ([#24167]). + * Introduced the `empty` function, the functional pair to `empty!` which returns a new, + empty container ([#24390]). + + * `similar(::Associative)` has been deprecated in favor of `empty(::Associative)`, and + `similar(::Associative, ::Pair{K, V})` has been deprecated in favour of + `empty(::Associative, K, V)` ([#24390]). + Command-line option changes --------------------------- diff --git a/base/abstractarray.jl b/base/abstractarray.jl index b605cdcb7f183..88d9226c5f76e 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -563,8 +563,26 @@ of the columns of `A`. would create an array of `Int`, initialized to zero, matching the indices of `A`. """ -similar(f, shape::Tuple) = f(to_shape(shape)) -similar(f, dims::DimOrInd...) = similar(f, dims) +similar(f, shape::Tuple) = f(to_shape(shape)) # doesn't share well with Associative +similar(f, dims::DimOrInd...) = similar(f, dims) # doesn't share well with Associative + +""" + empty(v::AbstractVector, [eltype]) + +Create an empty vector similar to `v`, optionally changing the `eltype`. + +# Examples + +```jldoctest +julia> empty([1.0, 2.0, 3.0]) +0-element Array{Float64,1} + +julia> empty([1.0, 2.0, 3.0], String) +0-element Array{String,1} +``` +""" +empty(a::AbstractVector) = empty(a, eltype(a)) +empty(a::AbstractVector, ::Type{T}) where {T} = Vector{T}() ## from general iterable to any array diff --git a/base/associative.jl b/base/associative.jl index 3e04c6adacf01..6ae8bc407318d 100644 --- a/base/associative.jl +++ b/base/associative.jl @@ -136,8 +136,42 @@ This includes arrays, where the keys are the array indices. """ pairs(collection) = Generator(=>, keys(collection), values(collection)) +""" + similar(a::Associative, [value_type=valtype(a)], [inds=keys(a)]) + +Create an `Associative` container with unitialized values of `value_type` and indices +`inds`. The second and third arguments are optional and default to the input's `valtype` and +`keys`, respectively. + +Custom `Associative` subtypes may choose which specific associative type is best suited to +return for the given value type and indices, by specializing on the three-argument +signature. The default is to return a `Dict`. +""" +# v1.0: similar(a::Associative) = similar(a, valtype(a), keys(a)) +similar(a::Associative, ::Type{T}) where {T} = similar(a, T, keys(a)) +similar(a::Associative, inds) = similar(a, valtype(a), inds) + +# disambiguation +similar(a::Associative, t::Tuple) = similar(a, valtype(a), t) +similar(a::Associative, d::DimOrInd) = similar(a, valtype(a), d) + +""" + empty(a::Associative, [index_type=keytype(a)], [value_type=valtype(a)]) + +Create an empty `Associative` container which can accept indices of type `index_type` and +values of type `value_type`. The second and third arguments are optional and default to the +input's `keytype` and `valtype`, respectively. (If only one of the two types is specified, +it is assumed to be the `value_type`, and the `index_type` we default to `keytype(a)`). + +Custom `Associative` subtypes may choose which specific associative type is best suited to +return for the given index and value types, by specializing on the three-argument signature. +The default is to return an empty `Dict`. +""" +empty(a::Associative) = empty(a, keytype(a), valtype(a)) +empty(a::Associative, ::Type{V}) where {V} = empty(a, keytype(a), V) # Note: this is the form which makes sense for `Vector`. + function copy(a::Associative) - b = similar(a) + b = empty(a) for (k,v) in a b[k] = v end @@ -403,7 +437,7 @@ Dict{Int64,String} with 1 entry: """ function filter(f, d::Associative) # don't just do filter!(f, copy(d)): avoid making a whole copy of d - df = similar(d) + df = empty(d) try for (k, v) in d if f(k => v) @@ -512,7 +546,7 @@ mutable struct ObjectIdDict <: Associative{Any,Any} ObjectIdDict(o::ObjectIdDict) = new(copy(o.ht)) end -similar(d::ObjectIdDict) = ObjectIdDict() +empty(d::ObjectIdDict, ::Type{Any}, ::Type{Any}) = ObjectIdDict() function rehash!(t::ObjectIdDict, newsz = length(t.ht)) t.ht = ccall(:jl_idtable_rehash, Any, (Any, Csize_t), t.ht, newsz) diff --git a/base/deepcopy.jl b/base/deepcopy.jl index 7ee72dc6926ce..2f22a32679697 100644 --- a/base/deepcopy.jl +++ b/base/deepcopy.jl @@ -105,7 +105,7 @@ function deepcopy_internal(x::Dict, stackdict::ObjectIdDict) return (stackdict[x] = copy(x)) end - dest = similar(x) + dest = empty(x) stackdict[x] = dest for (k, v) in x dest[deepcopy_internal(k, stackdict)] = deepcopy_internal(v, stackdict) diff --git a/base/deprecated.jl b/base/deprecated.jl index ee81e772f9360..d962fe58216de 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -2084,6 +2084,10 @@ end @deprecate range(start::DateTime, len::Integer) range(start, Dates.Day(1), len) @deprecate range(start::Date, len::Integer) range(start, Dates.Day(1), len) +# issue #24019 +@deprecate similar(a::Associative) empty(a) +@deprecate similar(a::Associative, ::Type{Pair{K,V}}) where {K, V} empty(a, K, V) + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/dict.jl b/base/dict.jl index 67f8fbfd98d34..6846633b5578b 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -111,6 +111,9 @@ mutable struct Dict{K,V} <: Associative{K,V} new(copy(d.slots), copy(d.keys), copy(d.vals), 0, d.count, d.age, d.idxfloor, d.maxprobe) end + function Dict{K, V}(slots, keys, ndel, count, age, idxfloor, maxprobe) where {K, V} + new(slots, keys, Vector{V}(length(keys)), ndel, count, age, idxfloor, maxprobe) + end end function Dict{K,V}(kv) where V where K h = Dict{K,V}() @@ -170,7 +173,7 @@ end # this is a special case due to (1) allowing both Pairs and Tuples as elements, # and (2) Pair being invariant. a bit annoying. function grow_to!(dest::Associative, itr) - out = grow_to!(similar(dest, Pair{Union{},Union{}}), itr, start(itr)) + out = grow_to!(empty(dest, Union{}, Union{}), itr, start(itr)) return isempty(out) ? dest : out end @@ -180,7 +183,7 @@ function grow_to!(dest::Associative{K,V}, itr, st) where V where K if isa(k,K) && isa(v,V) dest[k] = v else - new = similar(dest, Pair{typejoin(K,typeof(k)), typejoin(V,typeof(v))}) + new = empty(dest, typejoin(K,typeof(k)), typejoin(V,typeof(v))) copy!(new, dest) new[k] = v return grow_to!(new, itr, st) @@ -189,8 +192,23 @@ function grow_to!(dest::Associative{K,V}, itr, st) where V where K return dest end -similar(d::Dict{K,V}) where {K,V} = Dict{K,V}() -similar(d::Dict, ::Type{Pair{K,V}}) where {K,V} = Dict{K,V}() +function similar(a::Associative, ::Type{V}, inds) where {V} + K = eltype(inds) + tmp = Dict{K, Void}() + for i in inds + tmp[i] = nothing + end + return Dict{K, V}(tmp.slots, tmp.keys, tmp.ndel, tmp.count, tmp.age, + tmp.idxfloor, tmp.maxprobe) +end + +# Fast copy of hashed indices from a `Dict` +function similar(a::Associative, ::Type{V}, inds::KeyIterator{<:Dict{K}}) where {K, V} + Dict{K, V}(copy(inds.dict.slots), copy(inds.dict.keys), inds.dict.ndel, inds.dict.count, + inds.dict.age, inds.dict.idxfloor, inds.dict.maxprobe) +end + +empty(a::Associative, ::Type{K}, ::Type{V}) where {K, V} = Dict{K, V}() # conversion between Dict types function convert(::Type{Dict{K,V}},d::Associative) where V where K @@ -813,12 +831,7 @@ next(::ImmutableDict{K,V}, t) where {K,V} = (Pair{K,V}(t.key, t.value), t.parent done(::ImmutableDict, t) = !isdefined(t, :parent) length(t::ImmutableDict) = count(x->true, t) isempty(t::ImmutableDict) = done(t, start(t)) -function similar(t::ImmutableDict) - while isdefined(t, :parent) - t = t.parent - end - return t -end +empty(::ImmutableDict, ::Type{K}, ::Type{V}) where {K, V} = ImmutableDict{K,V}() -_similar_for(c::Dict, ::Type{P}, itr, isz) where {P<:Pair} = similar(c, P) +_similar_for(c::Dict, ::Type{Pair{K,V}}, itr, isz) where {K, V} = empty(c, K, V) _similar_for(c::Associative, T, itr, isz) = throw(ArgumentError("for Associatives, similar requires an element type of Pair;\n if calling map, consider a comprehension instead")) diff --git a/base/env.jl b/base/env.jl index 718c500257665..4fa664c428add 100644 --- a/base/env.jl +++ b/base/env.jl @@ -73,8 +73,6 @@ variables. """ const ENV = EnvDict() -similar(::EnvDict) = Dict{String,String}() - getindex(::EnvDict, k::AbstractString) = access_env(k->throw(KeyError(k)), k) get(::EnvDict, k::AbstractString, def) = access_env(k->def, k) get(f::Callable, ::EnvDict, k::AbstractString) = access_env(k->f(), k) diff --git a/base/exports.jl b/base/exports.jl index 0e30396010261..1fbede9660cfa 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -652,6 +652,7 @@ export deleteat!, eltype, empty!, + empty, endof, filter!, filter, diff --git a/base/pkg/query.jl b/base/pkg/query.jl index 0233d159186a6..2507fa36741e1 100644 --- a/base/pkg/query.jl +++ b/base/pkg/query.jl @@ -42,9 +42,9 @@ end # Specialized copy for the avail argument below because the deepcopy is slow function availcopy(avail) - new_avail = similar(avail) + new_avail = empty(avail) for (pkg, vers_avail) in avail - new_vers_avail = similar(vers_avail) + new_vers_avail = empty(vers_avail) for (version, pkg_avail) in vers_avail new_vers_avail[version] = copy(pkg_avail) end diff --git a/base/tuple.jl b/base/tuple.jl index 72f13b1b82aed..2572b8db3f393 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -328,3 +328,10 @@ any(x::Tuple{}) = false any(x::Tuple{Bool}) = x[1] any(x::Tuple{Bool, Bool}) = x[1]|x[2] any(x::Tuple{Bool, Bool, Bool}) = x[1]|x[2]|x[3] + +""" + empty(x::Tuple) + +Returns an empty tuple, `()`. +""" +empty(x::Tuple) = () diff --git a/base/weakkeydict.jl b/base/weakkeydict.jl index 1ff64f5b172b1..0067baaa9add0 100644 --- a/base/weakkeydict.jl +++ b/base/weakkeydict.jl @@ -65,8 +65,7 @@ function WeakKeyDict(kv) end end -similar(d::WeakKeyDict{K,V}) where {K,V} = WeakKeyDict{K,V}() -similar(d::WeakKeyDict, ::Type{Pair{K,V}}) where {K,V} = WeakKeyDict{K,V}() +empty(d::WeakKeyDict, ::Type{K}, ::Type{V}) where {K, V} = WeakKeyDict{K, V}() # conversion between Dict types function convert(::Type{WeakKeyDict{K,V}},d::Associative) where V where K diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 6e852a90c5b22..1d39c1e9c8a4d 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -870,10 +870,22 @@ end Base.convert(::Type{Array{T,n}}, a::Array{T,n}) where {T<:Number,n} = a Base.convert(::Type{Array{T,n}}, a::Array) where {T<:Number,n} = copy!(Array{T,n}(size(a)), a) - @test isa(similar(Dict(:a=>1, :b=>2.0), Pair{Union{},Union{}}), Dict{Union{}, Union{}}) + @test isa(empty(Dict(:a=>1, :b=>2.0), Union{}, Union{}), Dict{Union{}, Union{}}) end @testset "zero-dimensional copy" begin Z = Array{Int}(); Z[] = 17 @test Z == collect(Z) == copy(Z) end + +@testset "empty" begin + @test isempty([]) + v = [1, 2, 3] + v2 = empty(v) + v3 = empty(v, Float64) + @test !isempty(v) + empty!(v) + @test isempty(v) + @test isempty(v2::Vector{Int}) + @test isempty(v3::Vector{Float64}) +end diff --git a/test/dict.jl b/test/dict.jl index 8c0f22e383592..d36817d4e6a86 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -381,7 +381,7 @@ end a[1] = a a[a] = 2 - sa = similar(a) + sa = empty(a) @test isempty(sa) @test isa(sa, ObjectIdDict) @@ -430,6 +430,50 @@ end [v for (k, v) in d] == [d[x[1]] for (i, x) in enumerate(d)] end +@testset "similar" begin + d = Dict(:a=>2, :b=>3) + + # v1.0: d2 = similar(d) + # v1.0: @test d2 isa typeof(d) + # v1.0: @test length(keys(d2)) == 2 + # v1.0: @test :a ∈ keys(d2) + # v1.0: @test :b ∈ keys(d2) + + d3 = similar(d, Float64) + @test d3 isa Dict{Symbol, Float64} + @test length(keys(d3)) == 2 + @test :a ∈ keys(d3) + @test :b ∈ keys(d3) + + d4 = similar(d, [10, 20, 30]) + @test d4 isa Dict{Int, Int} + @test length(keys(d4)) == 3 + @test 10 ∈ keys(d4) + @test 20 ∈ keys(d4) + @test 30 ∈ keys(d4) + + d5 = similar(d, Float64, [10, 20, 30]) + @test d5 isa Dict{Int, Float64} + @test length(keys(d5)) == 3 + @test 10 ∈ keys(d5) + @test 20 ∈ keys(d5) + @test 30 ∈ keys(d5) + + d6 = similar(d, (10, 20, 30)) + @test d6 isa Dict{Int, Int} + @test length(keys(d6)) == 3 + @test 10 ∈ keys(d6) + @test 20 ∈ keys(d6) + @test 30 ∈ keys(d6) + + d7 = similar(d, Base.OneTo(3)) + @test d7 isa Dict{Int, Int} + @test length(keys(d7)) == 3 + @test 1 ∈ keys(d7) + @test 2 ∈ keys(d7) + @test 3 ∈ keys(d7) +end + @testset "generators, similar" begin d = Dict(:a=>"a") # TODO: restore when 0.7 deprecation is removed @@ -507,8 +551,8 @@ import Base.ImmutableDict @test get(d, k1, :default) === :default @test d1["key1"] === v1 @test d4["key1"] === v2 - @test similar(d3) === d - @test similar(d) === d + @test empty(d3) === d + @test empty(d) === d @test_throws KeyError d[k1] @test_throws KeyError d1["key2"] @@ -649,8 +693,8 @@ Dict(1 => rand(2,3), 'c' => "asdf") # just make sure this does not trigger a dep @test !isempty(wkd) wkd = empty!(wkd) - @test wkd == similar(wkd) - @test typeof(wkd) == typeof(similar(wkd)) + @test wkd == empty(wkd) + @test typeof(wkd) == typeof(empty(wkd)) @test length(wkd) == 0 @test isempty(wkd) @test isa(wkd, WeakKeyDict) diff --git a/test/tuple.jl b/test/tuple.jl index 438fe0e0d47ca..7fda7483c9572 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -83,6 +83,8 @@ end @test Tuple{Int,Vararg{Any}}.ninitialized == 1 @test Tuple{Any,Any,Vararg{Any}}.ninitialized == 2 end + + @test empty((1, 2.0, "c")) === () end @testset "size" begin