diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 96b1b834e7d43..db9096e67e20c 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -2126,28 +2126,34 @@ julia> hvncat(((3, 3), (3, 3), (6,)), true, a, b, c, d, e, f) 4 = elements in each 4d slice (4,) => shape = ((2, 1, 1), (3, 1), (4,), (4,)) with `rowfirst` = true """ -hvncat(::Tuple{}, ::Bool) = [] -hvncat(::Tuple{}, ::Bool, xs...) = [] -hvncat(::Tuple{Vararg{Any, 1}}, ::Bool, xs...) = vcat(xs...) # methods assume 2+ dimensions hvncat(dimsshape::Tuple, row_first::Bool, xs...) = _hvncat(dimsshape, row_first, xs...) hvncat(dim::Int, xs...) = _hvncat(dim, true, xs...) -_hvncat(::Union{Tuple, Int}, ::Bool) = [] +_hvncat(dimsshape::Union{Tuple, Int}, row_first::Bool) = _typed_hvncat(Any, dimsshape, row_first) _hvncat(dimsshape::Union{Tuple, Int}, row_first::Bool, xs...) = _typed_hvncat(promote_eltypeof(xs...), dimsshape, row_first, xs...) _hvncat(dimsshape::Union{Tuple, Int}, row_first::Bool, xs::T...) where T<:Number = _typed_hvncat(T, dimsshape, row_first, xs...) _hvncat(dimsshape::Union{Tuple, Int}, row_first::Bool, xs::Number...) = _typed_hvncat(promote_typeof(xs...), dimsshape, row_first, xs...) _hvncat(dimsshape::Union{Tuple, Int}, row_first::Bool, xs::AbstractArray...) = _typed_hvncat(promote_eltype(xs...), dimsshape, row_first, xs...) _hvncat(dimsshape::Union{Tuple, Int}, row_first::Bool, xs::AbstractArray{T}...) where T = _typed_hvncat(T, dimsshape, row_first, xs...) -typed_hvncat(::Type{T}, ::Tuple{}, ::Bool) where T = Vector{T}() -typed_hvncat(::Type{T}, ::Tuple{}, ::Bool, xs...) where T = Vector{T}() -typed_hvncat(T::Type, ::Tuple{Vararg{Any, 1}}, ::Bool, xs...) = typed_vcat(T, xs...) # methods assume 2+ dimensions typed_hvncat(T::Type, dimsshape::Tuple, row_first::Bool, xs...) = _typed_hvncat(T, dimsshape, row_first, xs...) typed_hvncat(T::Type, dim::Int, xs...) = _typed_hvncat(T, Val(dim), xs...) -_typed_hvncat(::Type{T}, ::Tuple{}, ::Bool) where T = Vector{T}() -_typed_hvncat(::Type{T}, ::Tuple{}, ::Bool, xs...) where T = Vector{T}() -_typed_hvncat(::Type{T}, ::Tuple{}, ::Bool, xs::Number...) where T = Vector{T}() +# 1-dimensional hvncat methods + +_typed_hvncat(::Type, ::Val{0}) = _typed_hvncat_0d_only_one() +_typed_hvncat(T::Type, ::Val{0}, x) = fill(convert(T, x)) +_typed_hvncat(T::Type, ::Val{0}, x::Number) = fill(convert(T, x)) +_typed_hvncat(T::Type, ::Val{0}, x::AbstractArray) = convert.(T, x) +_typed_hvncat(::Type, ::Val{0}, ::Any...) = _typed_hvncat_0d_only_one() +_typed_hvncat(::Type, ::Val{0}, ::Number...) = _typed_hvncat_0d_only_one() +_typed_hvncat(::Type, ::Val{0}, ::AbstractArray...) = _typed_hvncat_0d_only_one() + +_typed_hvncat_0d_only_one() = + throw(ArgumentError("a 0-dimensional array may only contain exactly one element")) + +_typed_hvncat(::Type{T}, ::Val{N}) where {T, N} = Array{T, N}(undef, ntuple(x -> 0, Val(N))) + function _typed_hvncat(::Type{T}, dims::Tuple{Vararg{Int, N}}, row_first::Bool, xs::Number...) where {T, N} A = Array{T, N}(undef, dims...) lengtha = length(A) # Necessary to store result because throw blocks are being deoptimized right now, which leads to excessive allocations @@ -2185,14 +2191,13 @@ function hvncat_fill!(A::Array, row_first::Bool, xs::Tuple) end _typed_hvncat(T::Type, dim::Int, ::Bool, xs...) = _typed_hvncat(T, Val(dim), xs...) # catches from _hvncat type promoters -_typed_hvncat(::Type{T}, ::Val) where T = Vector{T}() -_typed_hvncat(T::Type, ::Val{N}, xs::Number...) where N = _typed_hvncat(T, (ntuple(x -> 1, N - 1)..., length(xs)), false, xs...) function _typed_hvncat(::Type{T}, ::Val{N}, as::AbstractArray...) where {T, N} # optimization for arrays that can be concatenated by copying them linearly into the destination # conditions: the elements must all have 1- or 0-length dimensions above N for a ∈ as ndims(a) <= N || all(x -> size(a, x) == 1, (N + 1):ndims(a)) || - return _typed_hvncat(T, (ntuple(x -> 1, N - 1)..., length(as)), false, as...) + return _typed_hvncat(T, (ntuple(x -> 1, N - 1)..., length(as), 1), false, as...) + # the extra 1 is to avoid an infinite cycle end nd = max(N, ndims(as[1])) @@ -2246,6 +2251,31 @@ function _typed_hvncat(::Type{T}, ::Val{N}, as...) where {T, N} return A end + +# 0-dimensional cases for balanced and unbalanced hvncat method + +_typed_hvncat(T::Type, ::Tuple{}, ::Bool, x...) = _typed_hvncat(T, Val(0), x...) +_typed_hvncat(T::Type, ::Tuple{}, ::Bool, x::Number...) = _typed_hvncat(T, Val(0), x...) + + +# balanced dimensions hvncat methods + +_typed_hvncat(T::Type, dims::Tuple{Int}, ::Bool, as...) = _typed_hvncat_1d(T, dims[1], Val(false), as...) +_typed_hvncat(T::Type, dims::Tuple{Int}, ::Bool, as::Number...) = _typed_hvncat_1d(T, dims[1], Val(false), as...) + +function _typed_hvncat_1d(::Type{T}, ds::Int, ::Val{row_first}, as...) where {T, row_first} + lengthas = length(as) + ds > 0 || + throw(ArgumentError("`dimsshape` argument must consist of positive integers")) + lengthas == ds || + throw(ArgumentError("number of elements does not match `dimshape` argument; expected $ds, got $lengthas")) + if row_first + return _typed_hvncat(T, Val(2), as...) + else + return _typed_hvncat(T, Val(1), as...) + end +end + function _typed_hvncat(::Type{T}, dims::Tuple{Vararg{Int, N}}, row_first::Bool, as...) where {T, N} d1 = row_first ? 2 : 1 d2 = row_first ? 1 : 2 @@ -2308,7 +2338,16 @@ function _typed_hvncat(::Type{T}, dims::Tuple{Vararg{Int, N}}, row_first::Bool, return A end -function _typed_hvncat(::Type{T}, shape::Tuple{Vararg{Tuple, N}}, row_first::Bool, as...) where {T, N} + +# unbalanced dimensions hvncat methods + +function _typed_hvncat(T::Type, shape::Tuple{Tuple}, row_first::Bool, xs...) + length(shape[1]) > 0 || + throw(ArgumentError("each level of `shape` argument must have at least one value")) + return _typed_hvncat_1d(T, shape[1][1], Val(row_first), xs...) +end + +function _typed_hvncat(T::Type, shape::NTuple{N, Tuple}, row_first::Bool, as...) where {N} d1 = row_first ? 2 : 1 d2 = row_first ? 1 : 2 shape = collect(shape) # saves allocations later diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 65f9f4efa2cd3..a1c6dd1b22ce7 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -1342,6 +1342,7 @@ end end end +using Base: typed_hvncat @testset "hvncat" begin a = fill(1, (2,3,2,4,5)) b = fill(2, (1,1,2,4,5)) @@ -1389,7 +1390,68 @@ end @test [v v;;; fill(v, 1, 2)] == fill(v, 1, 2, 2) end - @test_throws BoundsError hvncat(((1, 2), (3,)), false, zeros(Int, 0, 0, 0), 7, 8) + # 0-dimension behaviors + # exactly one argument, placed in an array + # if already an array, copy, with type conversion as necessary + @test_throws ArgumentError hvncat(0) + @test hvncat(0, 1) == fill(1) + @test hvncat(0, [1]) == [1] + @test_throws ArgumentError hvncat(0, 1, 1) + @test_throws ArgumentError typed_hvncat(Float64, 0) + @test typed_hvncat(Float64, 0, 1) == fill(1.0) + @test typed_hvncat(Float64, 0, [1]) == Float64[1.0] + @test_throws ArgumentError typed_hvncat(Float64, 0, 1, 1) + @test_throws ArgumentError hvncat((), true) == [] + @test hvncat((), true, 1) == fill(1) + @test hvncat((), true, [1]) == [1] + @test_throws ArgumentError hvncat((), true, 1, 1) + @test_throws ArgumentError typed_hvncat(Float64, (), true) == Float64[] + @test typed_hvncat(Float64, (), true, 1) == fill(1.0) + @test typed_hvncat(Float64, (), true, [1]) == [1.0] + @test_throws ArgumentError typed_hvncat(Float64, (), true, 1, 1) + + # 1-dimension behaviors + # int form + @test hvncat(1) == [] + @test hvncat(1, 1) == [1] + @test hvncat(1, [1]) == [1] + @test hvncat(1, [1 2; 3 4]) == [1 2; 3 4] + @test hvncat(1, 1, 1) == [1 ; 1] + @test typed_hvncat(Float64, 1) == Float64[] + @test typed_hvncat(Float64, 1, 1) == Float64[1.0] + @test typed_hvncat(Float64, 1, [1]) == Float64[1.0] + @test typed_hvncat(Float64, 1, 1, 1) == Float64[1.0 ; 1.0] + # dims form + @test_throws ArgumentError hvncat((1,), true) + @test hvncat((2,), true, 1, 1) == [1; 1] + @test hvncat((2,), true, [1], [1]) == [1; 1] + @test_throws ArgumentError hvncat((2,), true, 1) + @test typed_hvncat(Float64, (2,), true, 1, 1) == Float64[1.0; 1.0] + @test typed_hvncat(Float64, (2,), true, [1], [1]) == Float64[1.0; 1.0] + @test_throws ArgumentError typed_hvncat(Float64, (2,), true, 1) + # row_first has no effect with just one dimension of the dims form + @test hvncat((2,), false, 1, 1) == [1; 1] + @test typed_hvncat(Float64, (2,), false, 1, 1) == Float64[1.0; 1.0] + # shape form + @test hvncat(((2,),), true, 1, 1) == [1 1] + @test hvncat(((2,),), true, [1], [1]) == [1 1] + @test_throws ArgumentError hvncat(((2,),), true, 1) + @test hvncat(((2,),), false, 1, 1) == [1; 1] + @test hvncat(((2,),), false, [1], [1]) == [1; 1] + @test typed_hvncat(Float64, ((2,),), true, 1, 1) == Float64[1.0 1.0] + @test typed_hvncat(Float64, ((2,),), true, [1], [1]) == Float64[1.0 1.0] + @test_throws ArgumentError typed_hvncat(Float64, ((2,),), true, 1) + @test typed_hvncat(Float64, ((2,),), false, 1, 1) == Float64[1.0; 1.0] + @test typed_hvncat(Float64, ((2,),), false, [1], [1]) == Float64[1.0; 1.0] + + # zero-value behaviors for int form above dimension zero + # e.g. [;;], [;;;], though that isn't valid syntax + @test [] == hvncat(1) isa Array{Any, 1} + @test Array{Any, 2}(undef, 0, 0) == hvncat(2) isa Array{Any, 2} + @test Array{Any, 3}(undef, 0, 0, 0) == hvncat(3) isa Array{Any, 3} + @test Int[] == typed_hvncat(Int, 1) isa Array{Int, 1} + @test Array{Int, 2}(undef, 0, 0) == typed_hvncat(Int, 2) isa Array{Int, 2} + @test Array{Int, 3}(undef, 0, 0, 0) == typed_hvncat(Int, 3) isa Array{Int, 3} end @testset "keepat!" begin