Skip to content

Commit c2113f8

Browse files
committed
Redesign around IdOffsetRange
1 parent e22f56e commit c2113f8

File tree

2 files changed

+150
-108
lines changed

2 files changed

+150
-108
lines changed

src/OffsetArrays.jl

Lines changed: 111 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
VERSION < v"0.7.0-beta2.199" && __precompile__()
2-
31
module OffsetArrays
42

53
using Base: Indices, tail, @propagate_inbounds
@@ -11,49 +9,92 @@ end
119

1210
export OffsetArray, OffsetVector
1311

12+
"""
13+
ro = IdOffsetRange(r::AbstractUnitRange, offset)
14+
15+
Construct an "identity offset range". Numerically, `collect(ro) == collect(r) .+ offset`,
16+
with the additional property that `axes(ro) = (ro,)`, which is where the "identity" comes from.
17+
"""
18+
struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T}
19+
parent::I
20+
offset::T
21+
end
22+
IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer) where T =
23+
IdOffsetRange{T,typeof(r)}(r, convert(T, offset))
24+
25+
@inline Base.axes(r::IdOffsetRange) = (Base.axes1(r),)
26+
@inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset)
27+
@inline Base.unsafe_indices(r::IdOffsetRange) = (r,)
28+
@inline Base.length(r::IdOffsetRange) = length(r.parent)
29+
30+
function Base.iterate(r::IdOffsetRange)
31+
ret = iterate(r.parent)
32+
ret === nothing && return nothing
33+
return (ret[1] + r.offset, ret[2])
34+
end
35+
function Base.iterate(r::IdOffsetRange, i) where T
36+
ret = iterate(r.parent, i)
37+
ret === nothing && return nothing
38+
return (ret[1] + r.offset, ret[2])
39+
end
40+
41+
@inline Base.first(r::IdOffsetRange) = first(r.parent) + r.offset
42+
@inline Base.last(r::IdOffsetRange) = last(r.parent) + r.offset
43+
44+
@propagate_inbounds Base.getindex(r::IdOffsetRange, i::Integer) = r.parent[i - r.offset] + r.offset
45+
@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::AbstractUnitRange{<:Integer})
46+
return r.parent[s .- r.offset] .+ r.offset
47+
end
48+
49+
Base.show(io::IO, r::IdOffsetRange) = print(io, first(r), ':', last(r))
50+
51+
# Optimizations
52+
@inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset)
53+
54+
## OffsetArray
1455
struct OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N}
1556
parent::AA
1657
offsets::NTuple{N,Int}
1758
end
1859
OffsetVector{T,AA<:AbstractArray} = OffsetArray{T,1,AA}
1960

20-
OffsetArray(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) where {T,N} =
61+
## OffsetArray constructors
62+
63+
offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent)
64+
offset(axparent::AbstractUnitRange, ax::Integer) = 1 - first(axparent)
65+
66+
function OffsetArray(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) where {T,N}
2167
OffsetArray{T,N,typeof(A)}(A, offsets)
68+
end
69+
OffsetArray(A::AbstractArray{T,0}, offsets::Tuple{}) where T =
70+
OffsetArray{T,0,typeof(A)}(A, ())
71+
2272
OffsetArray(A::AbstractArray{T,N}, offsets::Vararg{Int,N}) where {T,N} =
2373
OffsetArray(A, offsets)
74+
OffsetArray(A::AbstractArray{T,0}) where {T} = OffsetArray(A, ())
2475

2576
const ArrayInitializer = Union{UndefInitializer, Missing, Nothing}
2677
OffsetArray{T,N}(init::ArrayInitializer, inds::Indices{N}) where {T,N} =
27-
OffsetArray{T,N,Array{T,N}}(Array{T,N}(init, map(indexlength, inds)), map(indexoffset, inds))
78+
OffsetArray(Array{T,N}(init, map(indexlength, inds)), map(indexoffset, inds))
2879
OffsetArray{T}(init::ArrayInitializer, inds::Indices{N}) where {T,N} = OffsetArray{T,N}(init, inds)
2980
OffsetArray{T,N}(init::ArrayInitializer, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray{T,N}(init, inds)
3081
OffsetArray{T}(init::ArrayInitializer, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray{T,N}(init, inds)
31-
OffsetArray(A::AbstractArray{T,0}) where {T} = OffsetArray{T,0,typeof(A)}(A, ())
3282

3383
# OffsetVector constructors
3484
OffsetVector(A::AbstractVector, offset) = OffsetArray(A, offset)
3585
OffsetVector{T}(init::ArrayInitializer, inds::AbstractUnitRange) where {T} = OffsetArray{T}(init, inds)
3686

37-
# deprecated constructors
38-
using Base: @deprecate
39-
40-
@deprecate OffsetArray(::Type{T}, inds::Vararg{UnitRange{Int},N}) where {T,N} OffsetArray{T}(undef, inds)
41-
@deprecate OffsetVector(::Type{T}, inds::AbstractUnitRange) where {T} OffsetVector{T}(undef, inds)
42-
@deprecate OffsetArray{T,N}(inds::Indices{N}) where {T,N} OffsetArray{T,N}(undef, inds)
43-
@deprecate OffsetArray{T}(inds::Indices{N}) where {T,N} OffsetArray{T}(undef, inds)
44-
@deprecate OffsetArray{T,N}(inds::Vararg{AbstractUnitRange,N}) where {T,N} OffsetArray{T,N}(undef, inds)
45-
@deprecate OffsetArray{T}(inds::Vararg{AbstractUnitRange,N}) where {T,N} OffsetArray{T}(undef, inds)
46-
@deprecate OffsetVector{T}(inds::AbstractUnitRange) where {T} OffsetVector{T}(undef, inds)
47-
48-
# The next two are necessary for ambiguity resolution. Really, the
49-
# second method should not be necessary.
50-
OffsetArray(A::AbstractArray{T,0}, inds::Tuple{}) where {T} = OffsetArray{T,0,typeof(A)}(A, ())
51-
OffsetArray(A::AbstractArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called")
87+
# # The next two are necessary for ambiguity resolution. Really, the
88+
# # second method should not be necessary.
89+
# OffsetArray(A::AbstractArray{T,0}, inds::Tuple{}) where {T} = OffsetArray{T,0,typeof(A)}(A, ())
90+
# OffsetArray(A::AbstractArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called")
91+
5292
function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,AbstractUnitRange}) where {T,N}
53-
lA = map(indexlength, axes(A))
54-
lI = map(indexlength, inds)
93+
axparent = axes(A)
94+
lA = map(length, axparent)
95+
lI = map(length, inds)
5596
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"))
56-
OffsetArray(A, map(indexoffset, inds))
97+
OffsetArray(A, map(offset, axparent, inds))
5798
end
5899
OffsetArray(A::AbstractArray{T,N}, inds::Vararg{AbstractUnitRange,N}) where {T,N} =
59100
OffsetArray(A, inds)
@@ -62,8 +103,8 @@ OffsetArray(A::AbstractArray{T,N}, inds::Vararg{AbstractUnitRange,N}) where {T,N
62103
function OffsetArray(A::OffsetArray, inds::NTuple{N,AbstractUnitRange}) where {N}
63104
OffsetArray(parent(A), inds)
64105
end
65-
OffsetArray(A::OffsetArray{T,0}, inds::Tuple{}) where {T} = OffsetArray{T,0,typeof(A)}(parent(A), ())
66-
OffsetArray(A::OffsetArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called")
106+
OffsetArray(A::OffsetArray{T,0}, inds::Tuple{}) where {T} = OffsetArray(parent(A), ())
107+
# OffsetArray(A::OffsetArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called")
67108

68109
Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA))
69110
parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA
@@ -74,36 +115,26 @@ Base.parent(A::OffsetArray) = A.parent
74115
Base.eachindex(::IndexCartesian, A::OffsetArray) = CartesianIndices(axes(A))
75116
Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1)
76117

77-
Base.size(A::OffsetArray) = size(parent(A))
78-
Base.size(A::OffsetArray, d) = size(parent(A), d)
79-
80-
# Implementations of axes and indices1. Since bounds-checking is
81-
# performance-critical and relies on axes, these are usually worth
82-
# optimizing thoroughly.
83-
@inline Base.axes(A::OffsetArray, d) =
84-
1 <= d <= length(A.offsets) ? _slice(axes(parent(A))[d], A.offsets[d]) : (1:1)
85-
@inline Base.axes(A::OffsetArray) =
86-
_axes(axes(parent(A)), A.offsets) # would rather use ntuple, but see #15276
87-
@inline _axes(inds, offsets) =
88-
(_slice(inds[1], offsets[1]), _axes(tail(inds), tail(offsets))...)
89-
_axes(::Tuple{}, ::Tuple{}) = ()
90-
Base.axes1(A::OffsetArray{T,0}) where {T} = 1:1 # we only need to specialize this one
91-
92-
# Avoid the kw-arg on the range(r+x, length=length(r)) call in r .+ x
93-
@inline _slice(r, x) = IdentityUnitRange(Base._range(first(r) + x, nothing, nothing, length(r)))
94-
95-
const OffsetAxis = Union{Integer, UnitRange, Base.OneTo, IdentityUnitRange, Colon}
96-
function Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T
97-
B = similar(parent(A), T, dims)
98-
end
118+
@inline Base.size(A::OffsetArray) = size(parent(A))
119+
@inline Base.size(A::OffsetArray, d) = size(parent(A), d)
120+
121+
@inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets)
122+
@inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : Base.OneTo(1)
123+
@inline Base.axes1(A::OffsetArray{T,0}) where {T} = Base.OneTo(1) # we only need to specialize this one
124+
125+
const OffsetAxis = Union{Integer, UnitRange, Base.OneTo, IdentityUnitRange, IdOffsetRange, Colon}
126+
Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T =
127+
similar(parent(A), T, dims)
99128
function Base.similar(A::AbstractArray, ::Type{T}, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where T
100129
B = similar(A, T, map(indexlength, inds))
101-
OffsetArray(B, map(indexoffset, inds))
130+
return OffsetArray(B, map(offset, axes(B), inds))
102131
end
103132

104133
Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, inds)
105-
Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) =
106-
OffsetArray(reshape(A, map(indexlength, inds)), map(indexoffset, inds))
134+
function Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}})
135+
AR = reshape(A, map(indexlength, inds))
136+
return OffsetArray(AR, map(offset, axes(AR), inds))
137+
end
107138

108139
# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return
109140
# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...)))
@@ -116,8 +147,10 @@ Base.reshape(A::OffsetArray, ::Colon) = A
116147
Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(parent(A), inds)
117148
Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = reshape(parent(A), inds)
118149

119-
Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray} =
120-
OffsetArray(T(undef, map(indexlength, shape)), map(indexoffset, shape))
150+
function Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray}
151+
P = T(undef, map(indexlength, shape))
152+
OffsetArray(P, map(offset, axes(P), shape))
153+
end
121154

122155
Base.fill(v, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
123156
fill!(OffsetArray(Array{typeof(v), N}(undef, map(indexlength, inds)), map(indexoffset, inds)), v)
@@ -130,39 +163,44 @@ Base.trues(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
130163
Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
131164
fill!(OffsetArray(BitArray{N}(undef, map(indexlength, inds)), map(indexoffset, inds)), false)
132165

133-
@inline @propagate_inbounds function Base.getindex(A::OffsetArray{T,N}, I::Vararg{Int,N}) where {T,N}
134-
@boundscheck checkbounds(A, I...)
135-
@inbounds ret = parent(A)[offset(A.offsets, I)...]
136-
ret
137-
end
138-
@inline @propagate_inbounds function Base.getindex(A::OffsetVector, i::Int)
139-
@boundscheck checkbounds(A, i)
140-
@inbounds ret = parent(A)[offset(A.offsets, (i,))[1]]
141-
ret
142-
end
143-
@inline @propagate_inbounds function Base.getindex(A::OffsetArray, i::Int)
144-
@boundscheck checkbounds(A, i)
145-
@inbounds ret = parent(A)[i]
146-
ret
166+
## Indexing
167+
168+
# Note this gets the index of the parent *array*, not the index of the parent *range*
169+
# Here's how one can think about this:
170+
# Δi = i - first(r)
171+
# i′ = first(r.parent) + Δi
172+
# and one obtains the result below.
173+
parentindex(r::IdOffsetRange, i) = i - r.offset
174+
175+
@propagate_inbounds function Base.getindex(A::OffsetArray{T,N}, I::Vararg{Int,N}) where {T,N}
176+
J = map(parentindex, axes(A), I)
177+
return parent(A)[J...]
147178
end
148-
@inline @propagate_inbounds function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N}
179+
180+
@propagate_inbounds Base.getindex(A::OffsetVector, i::Int) = parent(A)[parentindex(Base.axes1(A), i)]
181+
@propagate_inbounds Base.getindex(A::OffsetArray, i::Int) = parent(A)[i]
182+
183+
@propagate_inbounds function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N}
149184
@boundscheck checkbounds(A, I...)
150-
@inbounds parent(A)[offset(A.offsets, I)...] = val
185+
J = @inbounds map(parentindex, axes(A), I)
186+
@inbounds parent(A)[J...] = val
151187
val
152188
end
153-
@inline @propagate_inbounds function Base.setindex!(A::OffsetVector, val, i::Int)
189+
190+
@propagate_inbounds function Base.setindex!(A::OffsetVector, val, i::Int)
154191
@boundscheck checkbounds(A, i)
155-
@inbounds parent(A)[offset(A.offsets, (i,))[1]] = val
192+
@inbounds parent(A)[parentindex(Base.axes1(A), i)] = val
156193
val
157194
end
158-
@inline @propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int)
195+
@propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int)
159196
@boundscheck checkbounds(A, i)
160197
@inbounds parent(A)[i] = val
161198
val
162199
end
163200

164201
# For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194
165202
Base.dataids(A::OffsetArray) = Base.dataids(parent(A))
203+
Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) === parent(src) ? src : Broadcast.unalias(dest, src)
166204

167205
### Special handling for AbstractRange
168206

@@ -175,10 +213,10 @@ Base.getindex(a::OffsetRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offs
175213
Base.getindex(a::OffsetRange, r::AbstractRange) = a.parent[r .- a.offsets[1]]
176214
Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets)
177215

178-
@inline @propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) =
216+
@propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) =
179217
OffsetArray(r[s.indices], s)
180218

181-
@inline @propagate_inbounds Base.getindex(r::StepRange, s::IIUR) =
219+
@propagate_inbounds Base.getindex(r::StepRange, s::IIUR) =
182220
OffsetArray(r[s.indices], s)
183221

184222
@inline @propagate_inbounds Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::IIUR) where T =
@@ -212,25 +250,13 @@ Base.empty!(A::OffsetVector) = (empty!(A.parent); A)
212250

213251
### Low-level utilities ###
214252

215-
# Computing a shifted index (subtracting the offset)
216-
@inline offset(offsets::NTuple{N,Int}, inds::NTuple{N,Int}) where {N} =
217-
(inds[1]-offsets[1], offset(Base.tail(offsets), Base.tail(inds))...)
218-
offset(::Tuple{}, ::Tuple{}) = ()
219-
220-
# Support trailing 1s
221-
@inline offset(offsets::Tuple{Vararg{Int}}, inds::Tuple{Vararg{Int}}) =
222-
(offset(offsets, Base.front(inds))..., inds[end])
223-
offset(offsets::Tuple{Vararg{Int}}, inds::Tuple{}) = error("inds cannot be shorter than offsets")
224-
225253
indexoffset(r::AbstractRange) = first(r) - 1
226254
indexoffset(i::Integer) = 0
227255
indexoffset(i::Colon) = 0
228256
indexlength(r::AbstractRange) = length(r)
229257
indexlength(i::Integer) = i
230258
indexlength(i::Colon) = Colon()
231259

232-
@eval @deprecate $(Symbol("@unsafe")) $(Symbol("@inbounds"))
233-
234260
function Base.showarg(io::IO, a::OffsetArray, toplevel)
235261
print(io, "OffsetArray(")
236262
Base.showarg(io, parent(a), false)

0 commit comments

Comments
 (0)