Skip to content

Commit 1263c2a

Browse files
authored
Reshape with an offset parent array (#245)
* reshape with an offset parent array * fix test on v1.0 * remove unnecesary functions * add test * remove indexoffset for Colon * fix reshape bug with differing sizes * check compatible sizes * Add length for CustomRange in tests * remove broken test for non-offsetarray type * remove unnecessary method
1 parent 5ee74cf commit 1263c2a

File tree

4 files changed

+80
-8
lines changed

4 files changed

+80
-8
lines changed

src/OffsetArrays.jl

+24-7
Original file line numberDiff line numberDiff line change
@@ -340,22 +340,39 @@ _similar_axes_or_length(AT, ax::I, ::I) where {I} = similar(AT, map(_indexlength
340340
# reshape accepts a single colon
341341
Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, inds)
342342
function Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}})
343-
AR = reshape(A, map(_indexlength, inds))
343+
AR = reshape(no_offset_view(A), map(_indexlength, inds))
344344
O = OffsetArray(AR, map(_offset, axes(AR), inds))
345345
return _popreshape(O, axes(AR), _filterreshapeinds(inds))
346346
end
347347

348348
# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return
349349
# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...)))
350+
# Short-circuit for AbstractVectors if the axes are compatible to get around the Base restriction
351+
# to 1-based vectors
352+
function _reshape(A::AbstractVector, inds::Tuple{OffsetAxis})
353+
@noinline throw_dimerr(ind::Integer) = throw(
354+
DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with length $ind"))
355+
@noinline throw_dimerr(ind) = throw(
356+
DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with indices $ind"))
357+
_checksize(first(inds), size(A,1)) || throw_dimerr(first(inds))
358+
A
359+
end
360+
_reshape(A, inds) = _reshape2(A, inds)
361+
_reshape2(A, inds) = reshape(A, inds)
362+
# avoid a stackoverflow by relegating to the parent if no_offset_view returns an offsetarray
363+
_reshape2(A::OffsetArray, inds) = reshape(parent(A), inds)
364+
_reshape_nov(A, inds) = _reshape(no_offset_view(A), inds)
365+
350366
Base.reshape(A::OffsetArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) =
351-
OffsetArray(reshape(parent(A), map(_indexlength, inds)), map(_indexoffset, inds))
367+
OffsetArray(_reshape(parent(A), inds), map(_toaxis, inds))
352368
# And for non-offset axes, we can just return a reshape of the parent directly
353-
Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = reshape(parent(A), inds)
354-
Base.reshape(A::OffsetArray, inds::Dims) = reshape(parent(A), inds)
355-
Base.reshape(A::OffsetArray, ::Colon) = reshape(parent(A), Colon())
369+
Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = _reshape_nov(A, inds)
370+
Base.reshape(A::OffsetArray, inds::Dims) = _reshape_nov(A, inds)
356371
Base.reshape(A::OffsetVector, ::Colon) = A
357-
Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(parent(A), inds)
358-
Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = reshape(parent(A), inds)
372+
Base.reshape(A::OffsetVector, ::Tuple{Colon}) = A
373+
Base.reshape(A::OffsetArray, ::Colon) = reshape(A, (Colon(),))
374+
Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(A, inds)
375+
Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = _reshape_nov(A, inds)
359376

360377
# permutedims in Base does not preserve axes, and can not be fixed in a non-breaking way
361378
# This is a stopgap solution

src/utils.jl

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
_indexoffset(r::AbstractRange) = first(r) - 1
44
_indexoffset(i::Integer) = 0
5-
_indexoffset(i::Colon) = 0
65
_indexlength(r::AbstractRange) = length(r)
76
_indexlength(i::Integer) = Int(i)
87
_indexlength(i::Colon) = Colon()
98

9+
# utility methods used in reshape
10+
# we don't use _indexlength in this to avoid converting the arguments to Int
11+
_checksize(ind::Integer, s) = ind == s
12+
_checksize(ind::AbstractUnitRange, s) = length(ind) == s
13+
14+
_toaxis(i::Integer) = Base.OneTo(i)
15+
_toaxis(i) = i
16+
1017
_strip_IdOffsetRange(r::IdOffsetRange) = parent(r)
1118
_strip_IdOffsetRange(r) = r
1219

test/customranges.jl

+5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ for Z in [:ZeroBasedRange, :ZeroBasedUnitRange]
6565
@boundscheck checkbounds(A, r)
6666
OffsetArrays._indexedby(A[r.a], axes(r))
6767
end
68+
69+
@eval Base.reshape(z::$Z, inds::Tuple{}) = reshape(parent(z), inds)
70+
@eval Base.reshape(z::$Z, inds::Tuple{Int, Vararg{Int}}) = reshape(parent(z), inds)
71+
@eval Base.reshape(z::$Z, inds::Tuple{Union{Int, AbstractUnitRange{<:Integer}}, Vararg{Union{Int, AbstractUnitRange{<:Integer}}}}) = reshape(parent(z), inds)
6872
end
6973

7074
# A basic range that does not have specialized vector indexing methods defined
@@ -75,6 +79,7 @@ struct CustomRange{T,A<:AbstractRange{T}} <: AbstractRange{T}
7579
end
7680
Base.parent(r::CustomRange) = r.a
7781
Base.size(r::CustomRange) = size(parent(r))
82+
Base.length(r::CustomRange) = length(parent(r))
7883
Base.axes(r::CustomRange) = axes(parent(r))
7984
Base.first(r::CustomRange) = first(parent(r))
8085
Base.last(r::CustomRange) = last(parent(r))

test/runtests.jl

+43
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,49 @@ end
17731773
@test axes(R) == (1:2, 1:3)
17741774
R = reshape(zeros(6,1), 1:2, :)
17751775
@test axes(R) == (1:2, 1:3)
1776+
1777+
r = OffsetArray(ZeroBasedRange(3:4), 1);
1778+
@test reshape(r, 2) == 3:4
1779+
@test reshape(r, (2,)) == 3:4
1780+
@test reshape(r, :) == 3:4
1781+
@test reshape(r, (:,)) == 3:4
1782+
1783+
# getindex for a reshaped array that wraps an offset array is broken on 1.0
1784+
if VERSION >= v"1.1"
1785+
@test reshape(r, (2,:,4:4)) == OffsetArray(reshape(3:4, 2, 1, 1), 1:2, 1:1, 4:4)
1786+
end
1787+
1788+
# reshape works even if the parent doesn't have 1-based indices
1789+
# this works even if the parent doesn't support the reshape
1790+
r = OffsetArray(IdentityUnitRange(0:1), -1)
1791+
@test reshape(r, 2) == 0:1
1792+
@test reshape(r, (2,)) == 0:1
1793+
@test reshape(r, :) == OffsetArray(0:1, -1:0)
1794+
@test reshape(r, (:,)) == OffsetArray(0:1, -1:0)
1795+
1796+
@test reshape(ones(2:3, 4:5), (2, :)) == ones(2,2)
1797+
1798+
# more than one colon is not allowed
1799+
@test_throws Exception reshape(ones(3:4, 4:5, 1:2), :, :, 2)
1800+
@test_throws Exception reshape(ones(3:4, 4:5, 1:2), :, 2, :)
1801+
1802+
A = OffsetArray(rand(4, 4), -1, -1);
1803+
B = reshape(A, (2, :))
1804+
@test axes(B, 1) == 1:2
1805+
@test axes(B, 2) == 1:8
1806+
1807+
# some more exotic vector types
1808+
r = OffsetVector(CustomRange(ZeroBasedRange(0:2)), -2)
1809+
r2 = reshape(r, :)
1810+
@test r2 == r
1811+
r2 = reshape(r, 3)
1812+
@test axes(r2, 1) == 1:3
1813+
@test r2 == no_offset_view(r)
1814+
@test_throws Exception reshape(r, length(r) + 1)
1815+
@test_throws Exception reshape(r, 1:length(r) + 1)
1816+
rp = parent(r)
1817+
@test axes(reshape(rp, 4:6), 1) == 4:6
1818+
@test axes(reshape(r, (3,1))) == (1:3, 1:1)
17761819
end
17771820

17781821
@testset "permutedims" begin

0 commit comments

Comments
 (0)