How to convert Vector{Any} to a narrower type?

Consider the following Julia code

julia> a = Any[1, 2, 3.0]
3-element Vector{Any}:
 1
 2
 3.0

julia> map(x->x, a)
3-element Vector{Real}:
 1
 2
 3.0

Here I abused map to convert an array of Any to the narrowest type Real that all elements belong to. Is there a less hacky way to do this, while retaining the automatic detection of actual types inside the array?

1 Like

You can broadcast identity.

6 Likes

I just came across this and thought it’s worth benchmarking. Looks like map is better suited for this:

I had thought map might be the best approach, but I looked at the code it produces:

julia> @code_typed map(identity, a)
CodeInfo(
1 ─ %1 = %new(Generator{Vector{Any}, typeof(identity)}, identity, A)::Generator{Vector{Any}, typeof(identity)}
│   %2 = invoke Base._collect(A::Vector{Any}, %1::Generator{Vector{Any}, typeof(identity)}, $(QuoteNode(EltypeUnknown()))::EltypeUnknown, $(QuoteNode(HasShape{1}()))::HasShape{1})::Vector
└──      return %2
)=>Vector

Ok, so first it makes a generator, then collects it. So let’s try that:

julia> a = Any[1, 2, 3.0]
3-element Vector{Any}:
 1
 2
 3.0

julia> @btime identity.(a)
  1.815 μs (18 allocations: 720 bytes)
3-element Vector{Real}:
 1
 2
 3.0

julia> @btime map(identity, a)
  588.475 ns (8 allocations: 256 bytes)
3-element Vector{Real}:
 1
 2
 3.0

julia> @btime collect((x for x in $a))
  258.529 ns (6 allocations: 224 bytes)
3-element Vector{Real}:
 1
 2
 3.0

I’m not sure why this is so much faster. But it seems to work for arrays too, no problem:

julia> b = [a randn(3)]
3×2 Matrix{Any}:
 1     0.105208
 2    -1.05589
 3.0   0.310332

julia> collect((x for x in b))
3×2 Matrix{Real}:
 1     0.105208
 2    -1.05589
 3.0   0.310332
1 Like

It would be nice if Base included something like

identity(x::Vector) = identity.(x)

My usage:
Using SplitApplyCombine.group to split vector of deals into a dictionary of vectors of deal subtypes. Then apply Value to each vector.

@>   group( typeof, deals )      Value.()

But the vectors produced by group are all of the parent {deal} type. To apply the right Value method I need to convert each Vector{deal} to a Vector{deal-subtype} using:

Value( x::Vector{deal} )      = Value( identity.(x))

It would be nice to instead write:

Value(::Vector{deal})   = Value ∘ identity 

It’s pretty simple to add identity.(...) explicitly before calling Value(...), no need to define new methods:

using DataPipes  # not sure which piping package you used, here showing example with DataPipes

@p group(typeof, deals) |> map(Value(identity.(_)))
# or
@p group(typeof, deals) .|> Value.(identity.(__))

Being explicit helps understanding what the code actually does.

That doesn’t use the argument at all, and returns the function instead of the modified vector.

identity is definitely the wrong function to overload: it’s specifically to always return its argument unmodified.
And defining a new function, making everyone aware of it – sounds way too much simply to avoid writing the dot ..

5 Likes
f(::Pair{Symbol,Int64})     = "pair_Symbol_Int64"
f(::Pair{Symbol,String})    = "pair_Symbol_String"
f(::Pair)                   = "pair"

[ Pair(:x,"a"),  Pair(:y,2.3)][1]    |>     f

returns pair. I’d like to return pair_Symbol_String

Is this possible ?

julia> Any[ Pair(:x,"a"),  Pair(:y,2.3)][1]    |>     f
"pair_Symbol_String"
1 Like
x =    [Pair(:a,1),Pair(:b,"s")]  
y = Any[Pair(:a,1),Pair(:b,"s")]

f.( y )                               #works
f.( convert(Vector{Any}, x) )         #doesn't work

how can I do it with applying a function to x ?

Any[x...] isn’t the same as Any[Pair(:a,1),Pair(:b,"s")]

You need to repackage them into better types, e.g.

julia> f.(map(p -> Pair(p...), x))
2-element Vector{String}:
 "pair_Symbol_Int64"
 "pair_Symbol_String"
1 Like

thanks

This issue is specific to Pair and a few other promotable types. IMO, it is pretty unusual that simply putting an object into array changes it:

julia> p1 = Pair(:x,"a")
julia> p2 = Pair(:y,2.3)
julia> [p1, p2]

julia> [p1, p2][1] === p1
false

But that’s how it is defined…

1 Like

I mean you can prevent it, but you need to define all the Pairs with a compatible type before.

julia> p1 = Pair{Symbol,Any}(:x,"a")
Pair{Symbol, Any}(:x, "a")

julia> p2 = Pair{Symbol,Any}(:y,2.3)
Pair{Symbol, Any}(:y, 2.3)

julia> [p1, p2][1] === p1
true