So this looks a bit more complicated but it should cover input functions which aren’t type safe.
For the type-safe condition there is only about 5% over head for these changes.
The mutating version is still like 6x slower than the non mutating version, the type broadening path is then about 2x slower than that.
"""
mapdict!(f, dict) -> dict
Takes the function f(value) and transforms the stored values with essentially value=f(value).
If the value returned by f(value) is of a differnet type than the input function used
must take the for dict = map(f,dict) which will mutate dict in a memory efficent manner
"""
function mapdict!(f, d::Dict{K,V}) where K where V
isempty(d) && return d
f_return_type = typeof(f(first(d)[2]))
if f_return_type == V
new_d=d
else
L=length(d.vals)
new_vals = Vector{f_return_type}(undef,L)
new_d = Dict{K, f_return_type}(d.slots, d.keys, new_vals, d.ndel, d.count, d.age, d.idxfloor, d.maxprobe)
end
ret = _mapdict_apply!(f, new_d,d.vals) # This function will return a Tuple if it isn't typestable
ret isa Dict && return ret# If ret is a Dict the function complete in a typesafe way
# If it didn't we have to broaden the type
ret= _mapdict_unioning!(f,d,ret)
return ret
end
"""
mapdict(f, dict) -> dict
Takes the function f(value) and returns a new Dict with the values transfor with new_value=f(value).
"""
function mapdict(f, d::Dict{K,V}) where K where V
isempty(d) && return d
f_return_type = typeof(f(first(d)[2]))
if f_return_type == V
new_d=Dict(d)
else
L=length(d.vals)
new_vals = Vector{f_return_type}(undef,L)
new_d = Dict{K, f_return_type}(copy(d.slots), copy(d.keys), new_vals, d.ndel, d.count, d.age, d.idxfloor, d.maxprobe)
end
ret = _mapdict_apply!(f, new_d,d.vals)
ret isa Dict && return ret # If ret is a Dict the function complete in a typesafe way
# If it didn't we have to broaden the type
ret= _mapdict_unioning!(f,d,ret)
return ret
end
@inline function _mapdict_apply!(f,d::Dict{K,V}, old_vals::Vector{Vold},i_start=d.idxfloor) where V where K where Vold
L = length(d.vals)
type_test(v)=false
if isconcretetype(V)
@inline type_test(v)= typeof(v)===V
else
@inline type_test(v)= typeof(v) <: V
end
i = i_start
vals = d.vals
@inbounds while i < L
(Base.isslotfilled(d, i) || (i+=1; continue )) && #This first line is to check the slot and iterate if it isn't used
(new_val = f(old_vals[i]); true) && type_test(new_val) && # This line is getting the new val checking the type
(vals[i] = new_val; true) && (i += 1; true) && continue #this line is finishing the iteration
# If anything fails above we return a tuple with the new type,dict, and the current index so we don't reapply the function
return (typeof(new_val),d,i)
end
return d
end
# This function will keep broadening the new type for 5 iterations then assume the output should be any
function _mapdict_unioning!(f, old_d::Dict{K,Vold},ret::Tuple{DataType,Dict{K,Vnew},Int}) where K where Vnew where Vold
num_types_before_any = 5
type_counter=0
union_type=Vnew
d=ret[2]
while ret isa Tuple && type_counter <= num_types_before_any
type_counter+=1
new_type=ret[1]
union_type=type_counter < num_types_before_any ? Union{union_type,new_type} : Any
d = Dict{K, union_type}(d.slots, d.keys, convert(Vector{union_type}, d.vals), d.ndel, d.count, d.age, d.idxfloor, d.maxprobe)
ret=_mapdict_apply!(f, d, old_d.vals,ret[3])
end
if ret isa Dict
return ret
else
#Function should never get here because Any should catch everything
error("mapdict could not converge on function output type")
end
end