-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preserve ranges in indexing with IdentityUnitRange(::Base.OneTo) #211
Changes from all commits
5004372
d91f7e9
bcc2462
faa553d
4ee234a
8dac4e5
f0f065b
c79f195
50dcc26
854bce2
a94a1e4
4c74874
e4e3786
009c9c9
e76b145
f2ff97d
a8df668
0eee8f8
4f594ef
80049e5
bd0c993
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -392,44 +392,92 @@ Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) | |
|
||
### Special handling for AbstractRange | ||
|
||
const OffsetRange{T} = OffsetArray{T,1,<:AbstractRange{T}} | ||
const OffsetUnitRange{T} = OffsetArray{T,1,<:AbstractUnitRange{T}} | ||
const OffsetRange{T} = OffsetVector{T,<:AbstractRange{T}} | ||
const OffsetUnitRange{T} = OffsetVector{T,<:AbstractUnitRange{T}} | ||
const IIUR = IdentityUnitRange{S} where S<:AbstractUnitRange{T} where T<:Integer | ||
|
||
Base.step(a::OffsetRange) = step(parent(a)) | ||
|
||
@propagate_inbounds function Base.getindex(a::OffsetRange, r::OffsetRange) | ||
OffsetArray(a.parent[r.parent .- a.offsets[1]], axes(r)) | ||
Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem correct: julia> ax = OffsetArrays.IdOffsetRange(Base.OneTo(5), -3)
OffsetArrays.IdOffsetRange(values=-2:2, indices=-2:2)
julia> axes(ax) == axes(ax.parent)
false There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand. Perhaps I'm missing something, could you expand on that example to illustrate the problem? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, sorry! |
||
|
||
# Certain special methods for linear indexing with integer ranges (or OffsetRanges) | ||
# These may bypass the default getindex(A, I...) pathway if the parent types permit this | ||
# For example AbstractUnitRanges and Arrays have special linear indexing behavior defined | ||
|
||
# If both the arguments are offset, we may unwrap the indices to call (::OffsetArray)[::AbstractRange{Int}] | ||
@propagate_inbounds function Base.getindex(A::OffsetArray, r::OffsetRange{Int}) | ||
_maybewrapoffset(A[parent(r)], axes(r)) | ||
end | ||
# If the indices are offset, we may unwrap them and pass the parent to getindex | ||
@propagate_inbounds function Base.getindex(A::AbstractRange, r::OffsetRange{Int}) | ||
_maybewrapoffset(A[parent(r)], axes(r)) | ||
end | ||
|
||
# An OffsetUnitRange might use the rapid getindex(::Array, ::AbstractUnitRange{Int}) for contiguous indexing | ||
@propagate_inbounds function Base.getindex(A::Array, r::OffsetUnitRange{Int}) | ||
B = A[_contiguousindexingtype(parent(r))] | ||
OffsetArray(B, axes(r)) | ||
end | ||
|
||
# avoid hitting the slow method getindex(::Array, ::AbstractRange{Int}) | ||
# instead use the faster getindex(::Array, ::UnitRange{Int}) | ||
@propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR}) | ||
B = A[_contiguousindexingtype(r)] | ||
_maybewrapoffset(B, axes(r)) | ||
end | ||
@propagate_inbounds function Base.getindex(a::OffsetRange, r::IdOffsetRange) | ||
OffsetArray(a.parent[r.parent .+ (r.offset - a.offsets[1])], axes(r)) | ||
|
||
# Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods | ||
@inline function Base.getindex(A::OffsetArray, r::AbstractUnitRange{Int}) | ||
@boundscheck checkbounds(A, r) | ||
# nD OffsetArrays do not have their linear indices shifted, so we may forward the indices provided to the parent | ||
@inbounds B = parent(A)[_contiguousindexingtype(r)] | ||
_maybewrapoffset(B, axes(r)) | ||
end | ||
@inline function Base.getindex(A::OffsetVector, r::AbstractUnitRange{Int}) | ||
@boundscheck checkbounds(A, r) | ||
# OffsetVectors may have their linear indices shifted, so we subtract the offset from the indices provided | ||
@inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] | ||
_maybewrapoffset(B, axes(r)) | ||
end | ||
|
||
# This method added mainly to index an OffsetRange with another range | ||
@inline function Base.getindex(A::OffsetVector, r::AbstractRange{Int}) | ||
@boundscheck checkbounds(A, r) | ||
@inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] | ||
_maybewrapoffset(B, axes(r)) | ||
end | ||
|
||
# In general we would pass through getindex(A, I...) which calls to_indices(A, I) and finally to_index(I) | ||
# An OffsetUnitRange{Int} has an equivalent IdOffsetRange with the same values and axes, | ||
# something similar also holds for OffsetUnitRange{BigInt} | ||
# We may replace the former with the latter in an indexing operation to obtain a performance boost | ||
@inline function Base.to_index(r::OffsetUnitRange{<:Union{Int,BigInt}}) | ||
of = first(axes(r,1)) - 1 | ||
IdOffsetRange(_subtractoffset(parent(r), of), of) | ||
end | ||
@propagate_inbounds Base.getindex(a::OffsetRange, r::AbstractRange) = _maybewrapaxes(a.parent[r .- a.offsets[1]], axes(r,1)) | ||
@propagate_inbounds Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], axes(r)) | ||
|
||
for OR in [:IIUR, :IdOffsetRange] | ||
for R in [:StepRange, :StepRangeLen, :LinRange, :UnitRange] | ||
@eval @propagate_inbounds Base.getindex(r::$R, s::$OR) = OffsetArray(r[UnitRange(s)], axes(s)) | ||
@eval @inline function Base.getindex(r::$R, s::$OR) | ||
@boundscheck checkbounds(r, s) | ||
@inbounds pr = r[UnitRange(s)] | ||
_maybewrapoffset(pr, axes(s,1)) | ||
end | ||
end | ||
|
||
# this method is needed for ambiguity resolution | ||
@eval @propagate_inbounds Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::$OR) where T = | ||
OffsetArray(r[UnitRange(s)], axes(s)) | ||
|
||
#= Integer UnitRanges may return an appropriate AbstractUnitRange{<:Integer}, as the result may be used in indexing, and | ||
indexing is faster with ranges =# | ||
@eval @propagate_inbounds function Base.getindex(r::UnitRange{<:Integer}, s::$OR) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not necessary anymore as |
||
rs = r[UnitRange(s)] | ||
offset_s = first(axes(s,1)) - 1 | ||
IdOffsetRange(UnitRange(rs .- offset_s), offset_s) | ||
@eval @inline function Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::$OR) where T | ||
@boundscheck checkbounds(r, s) | ||
@inbounds pr = r[UnitRange(s)] | ||
_maybewrapoffset(pr, axes(s,1)) | ||
end | ||
end | ||
|
||
# mapreduce is faster with an IdOffsetRange than with an OffsetUnitRange | ||
# We therefore convert OffsetUnitRanges to IdOffsetRanges with the same values and axes | ||
function Base.mapreduce(f, op, As::OffsetUnitRange{<:Integer}...; kw...) | ||
ofs = map(A -> first(axes(A,1)) - 1, As) | ||
AIds = map((A, of) -> IdOffsetRange(UnitRange(parent(A)) .- of, of), As, ofs) | ||
AIds = map((A, of) -> IdOffsetRange(_subtractoffset(parent(A), of), of), As, ofs) | ||
mapreduce(f, op, AIds...; kw...) | ||
end | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,17 +16,17 @@ _offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axpare | |
""" | ||
OffsetArrays.AxisConversionStyle(typeof(indices)) | ||
|
||
`AxisConversionStyle` declares if `indices` should be converted to a single `AbstractUnitRange{Int}` | ||
or to a `Tuple{Vararg{AbstractUnitRange{Int}}}` while flattening custom types into indices. | ||
This method is called after `to_indices(A::Array, axes(A), indices)` to provide | ||
`AxisConversionStyle` declares if `indices` should be converted to a single `AbstractUnitRange{Int}` | ||
or to a `Tuple{Vararg{AbstractUnitRange{Int}}}` while flattening custom types into indices. | ||
This method is called after `to_indices(A::Array, axes(A), indices)` to provide | ||
further information in case `to_indices` does not return a `Tuple` of `AbstractUnitRange{Int}`. | ||
|
||
Custom index types should extend `AxisConversionStyle` and return either `OffsetArray.SingleRange()`, | ||
which is the default, or `OffsetArray.TupleOfRanges()`. In the former case, the type `T` should | ||
define `Base.convert(::Type{AbstractUnitRange{Int}}, ::T)`, whereas in the latter it should define | ||
`Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, ::T)`. | ||
Custom index types should extend `AxisConversionStyle` and return either `OffsetArray.SingleRange()`, | ||
which is the default, or `OffsetArray.TupleOfRanges()`. In the former case, the type `T` should | ||
define `Base.convert(::Type{AbstractUnitRange{Int}}, ::T)`, whereas in the latter it should define | ||
`Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, ::T)`. | ||
|
||
An example of the latter is `CartesianIndices`, which is converted to a `Tuple` of | ||
An example of the latter is `CartesianIndices`, which is converted to a `Tuple` of | ||
`AbstractUnitRange{Int}` while flattening the indices. | ||
|
||
# Example | ||
|
@@ -75,10 +75,23 @@ function _checkindices(N::Integer, indices, label) | |
N == length(indices) || throw_argumenterror(N, indices, label) | ||
end | ||
|
||
_maybewrapaxes(A::AbstractVector, ::Base.OneTo) = no_offset_view(A) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
_maybewrapaxes(A::AbstractVector, ax) = OffsetArray(A, ax) | ||
|
||
_maybewrapoffset(r::AbstractUnitRange, of, ::Base.OneTo) = no_offset_view(r) | ||
_maybewrapoffset(r::AbstractVector, of, ::Base.OneTo) = no_offset_view(r) | ||
_maybewrapoffset(r::AbstractUnitRange, of, ::Any) = IdOffsetRange(UnitRange(r), of) | ||
_maybewrapoffset(r::AbstractVector, of, axs) = OffsetArray(r .+ of, axs) | ||
@inline _maybewrapoffset(r::AbstractVector, ax::Tuple{Any}) = _maybewrapoffset(r, ax[1]) | ||
@inline _maybewrapoffset(r::AbstractUnitRange{<:Integer}, ::Base.OneTo) = no_offset_view(r) | ||
@inline _maybewrapoffset(r::AbstractVector, ::Base.OneTo) = no_offset_view(r) | ||
@inline function _maybewrapoffset(r::AbstractUnitRange{<:Integer}, ax::AbstractUnitRange) | ||
of = first(ax) - 1 | ||
IdOffsetRange(_subtractoffset(r, of), of) | ||
end | ||
@inline _maybewrapoffset(r::AbstractVector, ax::AbstractUnitRange) = OffsetArray(r, ax) | ||
|
||
# These functions are equivalent to the broadcasted operation r .- of | ||
# However these ensure that the result is an AbstractRange even if a specific | ||
# broadcasting behavior is not defined for a custom type | ||
_subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of) | ||
_subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r)) | ||
|
||
if VERSION <= v"1.7.0-DEV.1039" | ||
_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = UnitRange{Int}(r) | ||
else | ||
_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is added to avoid cluttering the display with line numbers