Skip to content

Commit 44d2349

Browse files
authored
Make OffsetArray's axes offset themselves (#46)
* Make OffsetArray's axes offset themselves This is the companion PR to JuliaLang/julia#27038. * Display slices as simple ranges in show -- we allow constructing OffsetArrays in this manner * Add compatibility for 0.6
1 parent 2be1cff commit 44d2349

File tree

2 files changed

+63
-35
lines changed

2 files changed

+63
-35
lines changed

src/OffsetArrays.jl

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ using Compat: axes, CartesianIndices
88

99
export OffsetArray, OffsetVector, @unsafe
1010

11+
## Major change in 0.7: OffsetArray now uses Offset axes
12+
const AxisType = VERSION < v"0.7.0-DEV.5242" ? identity : Base.Slice
13+
1114
# TODO: just use .+
1215
# See https://github.com/JuliaLang/julia/pull/22932#issuecomment-330711997
1316
if VERSION < v"0.7.0-DEV.1759"
@@ -74,8 +77,8 @@ end
7477
OffsetArray(A::AbstractArray{T,0}, inds::Tuple{}) where {T} = OffsetArray{T,0,typeof(A)}(A, ())
7578
OffsetArray(A::AbstractArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called")
7679
function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,AbstractUnitRange}) where {T,N}
77-
lA = map(length, axes(A))
78-
lI = map(length, inds)
80+
lA = map(indexlength, axes(A))
81+
lI = map(indexlength, inds)
7982
lA == lI || throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices"))
8083
OffsetArray(A, map(indexoffset, inds))
8184
end
@@ -98,38 +101,48 @@ Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1)
98101
# performance-critical and relies on axes, these are usually worth
99102
# optimizing thoroughly.
100103
@inline Compat.axes(A::OffsetArray, d) =
101-
1 <= d <= length(A.offsets) ? plus(axes(parent(A))[d], A.offsets[d]) : (1:1)
104+
1 <= d <= length(A.offsets) ? AxisType(plus(axes(parent(A))[d], A.offsets[d])) : (1:1)
102105
@inline Compat.axes(A::OffsetArray) =
103106
_axes(axes(parent(A)), A.offsets) # would rather use ntuple, but see #15276
104107
@inline _axes(inds, offsets) =
105-
(plus(inds[1], offsets[1]), _axes(tail(inds), tail(offsets))...)
108+
(AxisType(plus(inds[1], offsets[1])), _axes(tail(inds), tail(offsets))...)
106109
_axes(::Tuple{}, ::Tuple{}) = ()
107110
Base.indices1(A::OffsetArray{T,0}) where {T} = 1:1 # we only need to specialize this one
108111

112+
113+
const OffsetAxis = Union{Integer, UnitRange, Base.Slice{<:UnitRange}, Base.OneTo}
109114
function Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T
110115
B = similar(parent(A), T, dims)
111116
end
112-
function Base.similar(A::AbstractArray, ::Type{T}, inds::Tuple{UnitRange,Vararg{UnitRange}}) where T
113-
B = similar(A, T, map(length, inds))
117+
function Base.similar(A::AbstractArray, ::Type{T}, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where T
118+
B = similar(A, T, map(indexlength, inds))
114119
OffsetArray(B, map(indexoffset, inds))
115120
end
116121

117-
Base.similar(::Type{T}, shape::Tuple{UnitRange,Vararg{UnitRange}}) where {T<:OffsetArray} =
118-
OffsetArray(T(map(length, shape)), map(indexoffset, shape))
119-
Base.similar(::Type{T}, shape::Tuple{UnitRange,Vararg{UnitRange}}) where {T<:Array} =
120-
OffsetArray(T(undef, map(length, shape)), map(indexoffset, shape))
121-
Base.similar(::Type{T}, shape::Tuple{UnitRange,Vararg{UnitRange}}) where {T<:BitArray} =
122-
OffsetArray(T(undef, map(length, shape)), map(indexoffset, shape))
122+
Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray} =
123+
OffsetArray(T(undef, map(indexlength, shape)), map(indexoffset, shape))
123124

125+
if VERSION < v"0.7.0-DEV.5242"
126+
# Reshape's methods in Base changed, so using the new definitions leads to ambiguities
124127
Base.reshape(A::AbstractArray, inds::Tuple{UnitRange,Vararg{UnitRange}}) =
125128
OffsetArray(reshape(A, map(length, inds)), map(indexoffset, inds))
126-
127129
Base.reshape(A::OffsetArray, inds::Tuple{UnitRange,Vararg{UnitRange}}) =
128130
OffsetArray(reshape(parent(A), map(length, inds)), map(indexoffset, inds))
129-
130131
function Base.reshape(A::OffsetArray, inds::Tuple{UnitRange,Vararg{Union{UnitRange,Int,Base.OneTo}}})
131132
throw(ArgumentError("reshape must supply UnitRange axes, got $(typeof(inds)).\n Note that reshape(A, Val{N}) is not supported for OffsetArrays."))
132133
end
134+
else
135+
Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) =
136+
OffsetArray(reshape(A, map(indexlength, inds)), map(indexoffset, inds))
137+
138+
# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return
139+
# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...)))
140+
Base.reshape(A::OffsetArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) =
141+
OffsetArray(reshape(parent(A), map(indexlength, inds)), map(indexoffset, inds))
142+
# And for non-offset axes, we can just return a reshape of the parent directly
143+
Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = reshape(parent(A), inds)
144+
Base.reshape(A::OffsetArray, inds::Dims) = reshape(parent(A), inds)
145+
end
133146

134147
if VERSION < v"0.7.0-DEV.4873"
135148
# Julia PR #26733 removed similar(f, ...) in favor of just using method extension directly
@@ -201,7 +214,7 @@ offset(offsets::Tuple{Vararg{Int}}, inds::Tuple{}) = error("inds cannot be short
201214

202215
indexoffset(r::AbstractRange) = first(r) - 1
203216
indexoffset(i::Integer) = 0
204-
indexlength(r::AbstractRange) = length(r)
217+
indexlength(r::AbstractRange) = Base._length(r)
205218
indexlength(i::Integer) = i
206219

207220
macro unsafe(ex)
@@ -290,8 +303,10 @@ if VERSION >= v"0.7.0-DEV.1790"
290303
toplevel && print(io, " with eltype ", eltype(a))
291304
end
292305
printindices(io::IO, ind1, inds...) =
293-
(print(io, ind1, ", "); printindices(io, inds...))
294-
printindices(io::IO, ind1) = print(io, ind1)
306+
(print(io, _unslice(ind1), ", "); printindices(io, inds...))
307+
printindices(io::IO, ind1) = print(io, _unslice(ind1))
308+
_unslice(x) = x
309+
_unslice(x::Base.Slice) = x.indices
295310
end
296311

297312
end # module

test/runtests.jl

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,22 @@ Ac[0,3,1] = 11
9494
@test_throws BoundsError S[CartesianIndex(1,1),0]
9595
@test_throws BoundsError S[CartesianIndex(1,1),2]
9696
@test eachindex(A) == 1:4
97-
@test eachindex(S) == CartesianIndices((0:1,3:4))
97+
@test eachindex(S) == CartesianIndices(Base.Slice.((0:1,3:4)))
9898

9999
# view
100+
const AxisType = VERSION < v"0.7.0-DEV.5242" ? identity : Base.Slice
100101
S = view(A, :, 3)
101102
@test S == OffsetArray([1,2], (A.offsets[1],))
102103
@test S[0] == 1
103104
@test S[1] == 2
104105
@test_throws BoundsError S[2]
105-
@test axes(S) === (0:1,)
106+
@test axes(S) === (AxisType(0:1),)
106107
S = view(A, 0, :)
107108
@test S == OffsetArray([1,3], (A.offsets[2],))
108109
@test S[3] == 1
109110
@test S[4] == 3
110111
@test_throws BoundsError S[1]
111-
@test axes(S) === (3:4,)
112+
@test axes(S) === (AxisType(3:4),)
112113
S = view(A, 0:0, 4)
113114
@test S == [3]
114115
@test S[1] == 3
@@ -127,7 +128,7 @@ S = view(A, :, :)
127128
@test S[0,4] == S[3] == 3
128129
@test S[1,4] == S[4] == 4
129130
@test_throws BoundsError S[1,1]
130-
@test axes(S) === (0:1, 3:4)
131+
@test axes(S) === AxisType.((0:1, 3:4))
131132

132133
# iteration
133134
let a
@@ -168,43 +169,53 @@ B = similar(A, (3,4))
168169
@test axes(B) === (Base.OneTo(3), Base.OneTo(4))
169170
B = similar(A, (-3:3,1:4))
170171
@test isa(B, OffsetArray{Int,2})
171-
@test axes(B) === (-3:3, 1:4)
172+
@test axes(B) === AxisType.((-3:3, 1:4))
172173
B = similar(parent(A), (-3:3,1:4))
173174
@test isa(B, OffsetArray{Int,2})
174-
@test axes(B) === (-3:3, 1:4)
175+
@test axes(B) === AxisType.((-3:3, 1:4))
175176

176177
# Reshape
177178
B = reshape(A0, -10:-9, 9:10)
178179
@test isa(B, OffsetArray{Int,2})
179180
@test parent(B) === A0
180-
@test axes(B) == (-10:-9, 9:10)
181+
@test axes(B) == AxisType.((-10:-9, 9:10))
181182
B = reshape(A, -10:-9, 9:10)
182183
@test isa(B, OffsetArray{Int,2})
183-
@test parent(B) === A0
184-
@test axes(B) == (-10:-9, 9:10)
184+
@test pointer(parent(B)) === pointer(A0)
185+
@test axes(B) == AxisType.((-10:-9, 9:10))
185186
b = reshape(A, -7:-4)
186-
@test axes(b) == (-7:-4,)
187+
@test axes(b) == (AxisType(-7:-4),)
187188
@test isa(parent(b), Vector{Int})
189+
@test pointer(parent(b)) === pointer(parent(A))
188190
@test parent(b) == A0[:]
189191
a = OffsetArray(rand(3,3,3), -1:1, 0:2, 3:5)
190-
@test_throws ArgumentError reshape(a, Val(2))
191-
@test_throws ArgumentError reshape(a, Val(4))
192+
if VERSION >= v"0.7.0-DEV.5242"
193+
# Offset axes are required for reshape(::OffsetArray, ::Val) support
194+
b = reshape(a, Val(2))
195+
@test isa(b, OffsetArray{Float64,2})
196+
@test pointer(parent(b)) === pointer(parent(a))
197+
@test axes(b) == AxisType.((-1:1, 1:9))
198+
b = reshape(a, Val(4))
199+
@test isa(b, OffsetArray{Float64,4})
200+
@test pointer(parent(b)) === pointer(parent(a))
201+
@test axes(b) == (axes(a)..., AxisType(1:1))
202+
end
192203

193204
# Indexing with OffsetArray axes
194205
i1 = OffsetArray([2,1], (-5,))
195206
i1 = OffsetArray([2,1], -5)
196207
b = A0[i1, 1]
197-
@test axes(b) === (-4:-3,)
208+
@test axes(b) === (AxisType(-4:-3),)
198209
@test b[-4] == 2
199210
@test b[-3] == 1
200211
b = A0[1,i1]
201-
@test axes(b) === (-4:-3,)
212+
@test axes(b) === (AxisType(-4:-3),)
202213
@test b[-4] == 3
203214
@test b[-3] == 1
204215
v = view(A0, i1, 1)
205-
@test axes(v) === (-4:-3,)
216+
@test axes(v) === (AxisType(-4:-3),)
206217
v = view(A0, 1:1, i1)
207-
@test axes(v) === (Base.OneTo(1), -4:-3)
218+
@test axes(v) === (Base.OneTo(1), AxisType(-4:-3))
208219

209220
# logical indexing
210221
@test A[A .> 2] == [3,4]
@@ -314,8 +325,10 @@ seek(io, 0)
314325
amin, amax = extrema(parent(A))
315326
@test clamp.(A, (amax+amin)/2, amax) == OffsetArray(clamp.(parent(A), (amax+amin)/2, amax), axes(A))
316327

317-
@test unique(A, 1) == parent(A)
318-
@test unique(A, 2) == parent(A)
328+
if VERSION >= v"0.7.0-DEV.5242"
329+
@test unique(A, 1) == OffsetArray(parent(A), 0, first(axes(A, 2)) - 1)
330+
@test unique(A, 2) == OffsetArray(parent(A), first(axes(A, 1)) - 1, 0)
331+
end
319332
v = OffsetArray(rand(8), (-2,))
320333
@test sort(v) == OffsetArray(sort(parent(v)), v.offsets)
321334
@test sortrows(A) == OffsetArray(sortrows(parent(A)), A.offsets)

0 commit comments

Comments
 (0)