Skip to content

Commit

Permalink
Added `empty
Browse files Browse the repository at this point in the history
 * `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)`.
  • Loading branch information
Andy Ferris committed Nov 4, 2017
1 parent 33763a0 commit 2e455e9
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 29 deletions.
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------
Expand Down
22 changes: 20 additions & 2 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
40 changes: 37 additions & 3 deletions base/associative.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion base/deepcopy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 24 additions & 11 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}()
Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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"))
2 changes: 0 additions & 2 deletions base/env.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ export
deleteat!,
eltype,
empty!,
empty,
endof,
filter!,
filter,
Expand Down
4 changes: 2 additions & 2 deletions base/pkg/query.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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) = ()
3 changes: 1 addition & 2 deletions base/weakkeydict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion test/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 49 additions & 5 deletions test/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ end
a[1] = a
a[a] = 2

sa = similar(a)
sa = empty(a)
@test isempty(sa)
@test isa(sa, ObjectIdDict)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions test/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2e455e9

Please sign in to comment.