Change type from Any to actual types

Hi,

I have an array of strings, which are replaced by ones or missings

julia> my_array = ["a", "b"]
2-element Array{String,1}:
"a"
"b"   

julia> my_replaced_array = replace(my_array, "a" => true, "b" => missing)
2-element Array{Any,1}:
true
missing 

but as you can see, the element type is now Any, instead of the desired Union{Missing, Bool}.

However, if I call an operation:

julia> .!my_replaced_array
2-element Array{Union{Missing, Bool},1}:
false
missing

The return type is the expected.

My question is if there is a way to get the right type of the elements of the replaced array from the elements of the array itself

One option is to convert after the replacement:

julia> my_replaced_array = Vector{Union{Bool,Missing}}(replace(my_array, "a" => true, "b" => missing))
2-element Array{Union{Missing, Bool},1}:
 true
     missing

I’m not sure it is the most efficient approach, though.

I’m looking for something more robust, which can work with any types.

But yeah, I think that is the solution for real cases, no need to develop a general case solution

You can try this trick

identity.(replace(my_array, "a" => true, "b" => missing))
4 Likes

Feels a bit clunky but seems to work:

julia> function replace2(x, pairs...)
         types = [pairs[j][2] for j = 1 : length(pairs)]
         T = eltype(types); 
         Vector{T}(replace(x, pairs...)); 
       end
replace2 (generic function with 1 method)
julia> replace2(my_array, "a" => true, "b" => missing)
2-element Array{Union{Missing, Bool},1}:
 true
     missing
julia> collect([true,missing])
2-element Vector{Union{Missing, Bool}}:
 true
     missing

how about this?

Note that [true, missing] is already of that element type:

julia> [true,missing]
2-element Array{Union{Missing, Bool},1}:
 true
     missing

and that collect doesn’t actually do the type narrowing:

julia> collect(Any[true,missing])
2-element Array{Any,1}:
 true
     missing

The identity.(...) “trick” is probably the most concise.

7 Likes

This has come up before, though I can’t find the exact threads. It might be worth having something in Base which takes an arbitrary array and converts it into the type that one would get from [a, b, c]


julia> t = Any[1, 2, 3]
3-element Array{Any,1}:
 1
 2
 3

julia> collect(t)
3-element Array{Any,1}:
 1
 2
 3

julia> identity.(t)
3-element Array{Int64,1}:
 1
 2
 3

julia> t = Any[1, 2, 3, 5.6]
4-element Array{Any,1}:
 1
 2
 3
 5.6

julia> identity.(t)
4-element Array{Real,1}:
 1
 2
 3
 5.6

However, identity does not seem to work in all cases. For example:

julia> identity.([true, 1])
2-element Array{Int64,1}:
 1
 1

Note that the [ syntax already does type promotion there, ie

julia> [true, 1]
2-element Array{Int64,1}:
 1
 1

so identity.(...) is a no-op. But if you are a bit more careful about inputs, you will see that

julia> identity.(Any[1, true])
2-element Array{Integer,1}:
    1
 true

works fine.

3 Likes

I guess what “works” means is ambiguous here. I expected the return type to be Vector{Union{Bool,Int64}} instead of Vector{Int}. But, yes, that’s a good point.

But, if you read carefully, you will see that you got Vector{Integer}, not Vector{Int}. Integer is a supertype for both Int and Bool.

5 Likes

Right - so it depends on whether the expected return type should be a Union of concrete types or their common supertype. Or is there a reason why the answer should always be the latter?

The solution is to do a pass-through of the data first using mapreduce(typeof, promote_typejoin, x) and then fill in an array with that type, I think.

I don’t think that there is a solution that is generally the preferred one — it depends on various circumstances. Eg if I know there are a “few” concrete types, I would prefer Union, but if there are many, I would hope that the implementation gives up sooner.