Skip to content

Commit 90a71e2

Browse files
Add view, axes, lastindex, range indexing for ragged types
Fixes issues reported by @JoshuaLampert: - Add view(r, :, i) and view(r, I, i) for ragged arrays - Add axes(r, d) and lastindex(r, d) so `end` works in indexing - Add range indexing: r[1:3, i], r[:, 2:end] - Note: `end` in first dim uses max inner array length (not per-column like v3's RaggedEnd), so r[end, i] may throw BoundsError for shorter inner arrays. Use r[:, i] for safe column access. Tests: 243 pass (up from 212). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 78b13e9 commit 90a71e2

File tree

2 files changed

+110
-12
lines changed

2 files changed

+110
-12
lines changed

lib/RecursiveArrayToolsRaggedArrays/src/RecursiveArrayToolsRaggedArrays.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,21 @@ Base.iterate(r::AbstractRaggedVectorOfArray, state) = iterate(r.u, state)
267267
Base.firstindex(r::AbstractRaggedVectorOfArray) = firstindex(r.u)
268268
Base.lastindex(r::AbstractRaggedVectorOfArray) = lastindex(r.u)
269269

270+
# lastindex with dimension — needed for `end` in multi-index expressions like r[end, 2]
271+
# dim N (last) = number of inner arrays; dim 1..N-1 = ragged, use max across inner arrays
272+
function Base.lastindex(r::AbstractRaggedVectorOfArray{T, N}, d::Int) where {T, N}
273+
if d == N
274+
return length(r.u)
275+
else
276+
return isempty(r.u) ? 0 : maximum(size(u, d) for u in r.u)
277+
end
278+
end
279+
280+
# axes with dimension — needed for `end` translation and range indexing
281+
function Base.axes(r::AbstractRaggedVectorOfArray{T, N}, d::Int) where {T, N}
282+
return Base.OneTo(lastindex(r, d))
283+
end
284+
270285
Base.keys(r::AbstractRaggedVectorOfArray) = keys(r.u)
271286
Base.eachindex(r::AbstractRaggedVectorOfArray) = eachindex(r.u)
272287

@@ -422,6 +437,45 @@ function Base.getindex(r::RaggedDiffEqArray, i::Int, ::Colon)
422437
return [u[i] for u in r.u]
423438
end
424439

440+
# Range indexing into inner arrays: r[1:3, i] or r[2:end, i]
441+
function Base.getindex(
442+
r::AbstractRaggedVectorOfArray, I::AbstractRange, col::Int
443+
)
444+
return r.u[col][I]
445+
end
446+
447+
# r[1:end, :] — range of components across all columns
448+
function Base.getindex(
449+
r::AbstractRaggedVectorOfArray, I::AbstractRange, ::Colon
450+
)
451+
return [u[I] for u in r.u]
452+
end
453+
454+
# A[:, range] — column subset with UnitRange (needed for A[:, 2:end])
455+
function Base.getindex(
456+
r::RaggedVectorOfArray, ::Colon, I::AbstractRange
457+
)
458+
return RaggedVectorOfArray(r.u[I])
459+
end
460+
function Base.getindex(
461+
r::RaggedDiffEqArray, ::Colon, I::AbstractRange
462+
)
463+
return RaggedDiffEqArray(r.u[I], r.t[I], r.p, r.sys;
464+
discretes = r.discretes, interp = r.interp, dense = r.dense)
465+
end
466+
467+
# ═══════════════════════════════════════════════════════════════════════════════
468+
# Views
469+
# ═══════════════════════════════════════════════════════════════════════════════
470+
471+
function Base.view(r::AbstractRaggedVectorOfArray, ::Colon, i::Int)
472+
return view(r.u[i], :)
473+
end
474+
475+
function Base.view(r::AbstractRaggedVectorOfArray, I, i::Int)
476+
return view(r.u[i], I)
477+
end
478+
425479
# ═══════════════════════════════════════════════════════════════════════════════
426480
# Symbolic Indexing Dispatch
427481
# ═══════════════════════════════════════════════════════════════════════════════

lib/RecursiveArrayToolsRaggedArrays/test/runtests.jl

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,23 @@ using Test
382382
@test f[:, 1] == [1.0]
383383
@test f[:, 2] == [2.0, 3.0]
384384

385+
# view also respects actual inner array size
386+
@test length(view(f, :, 1)) == 1
387+
@test length(view(f, :, 2)) == 2
388+
@test view(f, :, 1) == [1.0]
389+
@test view(f, :, 2) == [2.0, 3.0]
390+
@test collect(view(f, :, 1)) == f[:, 1]
391+
@test collect(view(f, :, 2)) == f[:, 2]
392+
385393
f2 = RaggedVectorOfArray([[1.0, 2.0], [3.0]])
386394
@test length(f2[:, 1]) == 2
387395
@test length(f2[:, 2]) == 1
388396
@test f2[:, 1] == [1.0, 2.0]
389397
@test f2[:, 2] == [3.0]
398+
@test length(view(f2, :, 1)) == 2
399+
@test length(view(f2, :, 2)) == 1
400+
@test view(f2, :, 1) == [1.0, 2.0]
401+
@test view(f2, :, 2) == [3.0]
390402
end
391403

392404
@testset "v3 end indexing with ragged arrays (ported)" begin
@@ -399,28 +411,60 @@ using Test
399411
@test ragged[2, 2] == 4.0
400412
@test ragged[3, 3] == 8.0
401413

414+
# `end` in the last (column) dimension
415+
@test ragged[:, end] == [6.0, 7.0, 8.0, 9.0]
416+
@test ragged[:, (end - 1):end] isa RaggedVectorOfArray
417+
@test length(ragged[:, (end - 1):end]) == 2
418+
419+
# `end` in first dim = max inner array length (4 here)
420+
# So ragged[end, 1] = ragged[4, 1] which is out of bounds for inner array 1
421+
@test_throws BoundsError ragged[end, 1] # inner[1] has only 2 elements
422+
@test_throws BoundsError ragged[end, 2] # inner[2] has only 3 elements
423+
@test ragged[end, 3] == 9.0 # inner[3] has 4 elements, end=4 works
424+
425+
# Range indexing into inner arrays
426+
@test ragged[1:2, 1] == [1.0, 2.0]
427+
@test ragged[1:3, 2] == [3.0, 4.0, 5.0]
428+
@test ragged[1:4, 3] == [6.0, 7.0, 8.0, 9.0]
429+
# 1:end uses max inner length (4), so only works for longest column
430+
@test_throws BoundsError ragged[1:end, 1] # 1:4 but inner[1] has 2
431+
@test_throws BoundsError ragged[1:end, 2] # 1:4 but inner[2] has 3
432+
@test ragged[1:end, 3] == [6.0, 7.0, 8.0, 9.0] # 1:4 matches inner[3]
433+
402434
# Colon returns actual arrays
403435
@test ragged[:, 1] == [1.0, 2.0]
404436
@test ragged[:, 2] == [3.0, 4.0, 5.0]
405437
@test ragged[:, 3] == [6.0, 7.0, 8.0, 9.0]
406438

407-
# Subset selection
408-
r_sub = ragged[:, [2, 3]]
409-
@test r_sub isa RaggedVectorOfArray
410-
@test r_sub[:, 1] == [3.0, 4.0, 5.0]
411-
@test r_sub[:, 2] == [6.0, 7.0, 8.0, 9.0]
439+
# Subset selection via range
440+
@test ragged[:, 2:end] isa RaggedVectorOfArray
441+
@test ragged[:, 2:end][:, 1] == [3.0, 4.0, 5.0]
412442

413443
ragged2 = RaggedVectorOfArray([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0], [7.0, 8.0, 9.0]])
414-
@test ragged2[4, 1] == 4.0
415-
@test ragged2[2, 2] == 6.0
416-
@test ragged2[3, 3] == 9.0
417-
@test ragged2[3, 1] == 3.0
418-
@test ragged2[1, 2] == 5.0
419-
@test ragged2[2, 3] == 8.0
420-
@test ragged2[2, 1] == 2.0
444+
# end in first dim = max length = 4
445+
@test ragged2[end, 1] == 4.0 # inner[1] has 4 elements
446+
@test_throws BoundsError ragged2[end, 2] # inner[2] has only 2
447+
@test_throws BoundsError ragged2[end, 3] # inner[3] has only 3
448+
@test ragged2[end - 1, 1] == 3.0 # end-1 = 3
449+
@test_throws BoundsError ragged2[end - 1, 2] # 3 > length([5,6])
450+
@test ragged2[end - 1, 3] == 9.0 # 3 <= length([7,8,9])
421451
@test ragged2[:, 1] == [1.0, 2.0, 3.0, 4.0]
422452
@test ragged2[:, 2] == [5.0, 6.0]
423453
@test ragged2[:, 3] == [7.0, 8.0, 9.0]
454+
# Explicit range indexing (not using end)
455+
@test ragged2[1:4, 1] == [1.0, 2.0, 3.0, 4.0]
456+
@test ragged2[1:2, 2] == [5.0, 6.0]
457+
@test ragged2[1:3, 3] == [7.0, 8.0, 9.0]
458+
@test ragged2[2:4, 1] == [2.0, 3.0, 4.0]
459+
@test ragged2[2:2, 2] == [6.0]
460+
@test ragged2[2:3, 3] == [8.0, 9.0]
461+
# end in last dim works fine
462+
@test ragged2[:, end] == [7.0, 8.0, 9.0]
463+
@test ragged2[:, 2:end] isa RaggedVectorOfArray
464+
# end in first dim = 4 (max), 1:(end-1) = 1:3
465+
@test ragged2[1:(end - 1), 1] == [1.0, 2.0, 3.0]
466+
@test_throws BoundsError ragged2[1:(end - 1), 2] # 1:3 but inner[2] has 2
467+
@test ragged2[1:(end - 1), 3] == [7.0, 8.0, 9.0] # 1:3 matches inner[3]
424468
end
425469

426470
@testset "v3 push! making array ragged (ported)" begin

0 commit comments

Comments
 (0)