Skip to content

Commit f983f9c

Browse files
committed
fix jagged vector instantiation from length-iterable
1 parent 6ed2048 commit f983f9c

File tree

2 files changed

+63
-5
lines changed

2 files changed

+63
-5
lines changed

src/jaggedvector.jl

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,31 @@ JaggedVector(data::Vector{T}, offsets::Vector{Int}) where T = JaggedVector{T}(da
6666
# ::: constructor for "empty" jagged vector with specified element lengths :::
6767
"""
6868
JaggedVector{T}(lengths::AbstractVector{<:Integer})
69+
JaggedVector{T}(lengths_iter)
6970
70-
Return a `JaggedVector{T}` with iterant lengths given by `lengths`, but uninitialized data.
71+
Return a `JaggedVector{T}` with iterant lengths given by `lengths` (or by an iterable of
72+
lengths `lengths_iter`, each returning an integer), but uninitialized data.
7173
This is useful for creating empty jagged vectors with known iterant lengths but
7274
uninitialized iterant data. The elements of `lengths` must be non-negative valued.
7375
7476
## Example
7577
The following creates a 3-element jagged vector, whose iterants have lengths 3, 2, and 4,
7678
respectively, and with uninitialized data for each iterant:
7779
```julia
78-
jv = JaggedVector{Int}([3, 2, 4])
80+
julia> jv = JaggedVector{Float64}([3, 2, 4])
81+
3-element JaggedVector{Float64}:
82+
[5.0e-324, 5.0e-324, 5.0e-310]
83+
[2.6e-322, 6.0e-310]
84+
[6.0-310, 2.6e-322, 0.0, 0.0]
7985
```
8086
"""
8187
function JaggedVector{T}(lengths::AbstractVector{<:Integer}) where T
8288
# create a jagged vector with specified vector lengths, but uninitialized data (i.e., to
8389
# create empty jagged vectors, where we know the length of the vectors it iterates, but
8490
# don't specify the data of the iterants initially)
91+
return _jaggedvector_from_lengths(T, lengths)
92+
end
93+
function _jaggedvector_from_lengths(::Type{T}, lengths) where T
8594
offsets = Vector{Int}(undef, length(lengths) + 1)
8695
last_offset = offsets[1] = 1
8796
for (i, l) in enumerate(lengths)
@@ -93,7 +102,6 @@ function JaggedVector{T}(lengths::AbstractVector{<:Integer}) where T
93102
data = Vector{T}(undef, last_offset - 1) # uninitialized data
94103
return JaggedVector{T}(data, offsets, Val(:unsafe))
95104
end
96-
97105
JaggedVector{T}() where T = JaggedVector{T}(Vector{T}(), [1]) # empty jagged vector
98106
JaggedVector() = JaggedVector{Any}()
99107

@@ -149,8 +157,28 @@ _possibly_unsafe_copyto!(dst::Vector, i, src::Vector, j, N) = unsafe_copyto!(dst
149157
Return a `JaggedVector{T}` from an iterable of iterables `iter`, whose inner iterable have
150158
elements convertible to type `T`.
151159
"""
152-
JaggedVector{T}(iter) where T = convert(JaggedVector{T}, iter)
153-
JaggedVector(iter) = JaggedVector{eltype(first(first(iter)))}(iter)
160+
function JaggedVector{T}(iter) where T
161+
E = typeof(first(iter))
162+
if E <: Integer
163+
# interpret as lengths of iterants, dispatching to `_jaggedvector_from_lengths`
164+
return _jaggedvector_from_lengths(T, iter)
165+
elseif E <: Number
166+
error(lazy"cannot construct a JaggedVector from iterable of type $E; input should be an iterable of iterables or an iterable of integer lengths")
167+
else
168+
return convert(JaggedVector{T}, iter)
169+
end
170+
end
171+
function JaggedVector(iter)
172+
E = typeof(first(iter))
173+
if E <: Integer
174+
return _jaggedvector_from_lengths(Any, iter) # cannot do better than `Any` here
175+
elseif E <: Number
176+
error(lazy"cannot construct a JaggedVector from iterable of type $E; input should be an iterable of iterables or an iterable of integer lengths")
177+
else
178+
T = typeof(first(first(iter)))
179+
return convert(JaggedVector{T}, iter)
180+
end
181+
end
154182

155183
function Base.convert(::Type{JaggedVector{T}}, iter) where T
156184
Nᵥ = length(iter)

test/jaggedvector.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,36 @@ using Crystalline.Jagged: JaggedVector
512512
@test all(isempty(jv[i]) for i in 1:3)
513513
@test length(jv.data) == 0
514514
end
515+
516+
@testset "instantiation from _iterator_ of lengths" begin
517+
iter = (i^2 for i in 1:3)
518+
jv = JaggedVector{Int}(iter) # must be interpreted as JaggedVector{…}(lengths)
519+
@test length(jv) == 3
520+
@test length(jv[1]) == 1
521+
@test length(jv[2]) == 4
522+
@test length(jv[3]) == 9
523+
524+
jvc = JaggedVector{ComplexF64}(iter) # specify element type
525+
@test jvc isa JaggedVector{ComplexF64}
526+
@test length(jvc) == 3
527+
@test length(jvc[1]) == 1
528+
@test length(jvc[2]) == 4
529+
@test length(jvc[3]) == 9
530+
531+
@test JaggedVector(iter) isa JaggedVector{Any} # default to Any eltype, if unspecified
532+
533+
iter = (Float64(i) for i in 1:2) # not allowed: iterable of non-integers
534+
@test_throws ErrorException JaggedVector{Int}(iter)
535+
@test_throws ErrorException JaggedVector(iter)
536+
537+
iter = ((i*1.0im for _ in 1) for i in 1:2) # allowed: iterable of iterables
538+
jv_iter = JaggedVector{ComplexF64}(iter)
539+
@test length(jv_iter) == 2
540+
@test jv_iter[1] == [1.0im]
541+
@test jv_iter[2] == [2.0im]
542+
543+
@test @inferred JaggedVector(iter) isa JaggedVector{ComplexF64} # type inferred correctly
544+
end
515545
end
516546

517547
@testset "Advanced features & implementation details" begin

0 commit comments

Comments
 (0)