Skip to content

Commit 73f7acd

Browse files
authored
Fix no_offset_view for Slice (#376)
Currently, the implementation of `no_offset_view` for a `Slice` doesn't actually ensure that there is no offset. This is because `Base.Slice(UnitRange(a))` has `UnitRange(a)` as its axes, which are usually offset. This PR changes the behavior to return a `UnitRange(a)` instead. Fixes #375 The flip side of the current implementation is that the `Slice` information is lost, so `IndexStyle` of 2D views might become `IndexCartesian` once they are routed through `no_offset_view`. ```julia julia> arr = OffsetArray(reshape(1:15, 3, 5), 2, 3) 3×5 OffsetArray(reshape(::UnitRange{Int64}, 3, 5), 3:5, 4:8) with eltype Int64 with indices 3:5×4:8: 1 4 7 10 13 2 5 8 11 14 3 6 9 12 15 julia> V = view(arr, :, :) 3×5 view(OffsetArray(reshape(::UnitRange{Int64}, 3, 5), 3:5, 4:8), :, :) with eltype Int64 with indices 3:5×4:8: 1 4 7 10 13 2 5 8 11 14 3 6 9 12 15 julia> IndexStyle(V) IndexLinear() julia> V_nooff = OffsetArrays.no_offset_view(V) 3×5 view(OffsetArray(reshape(::UnitRange{Int64}, 3, 5), 3:5, 4:8), 3:5, 4:8) with eltype Int64: 1 4 7 10 13 2 5 8 11 14 3 6 9 12 15 julia> IndexStyle(V_nooff) IndexCartesian() ``` An alternate approach might be to shift the parent to one-based indices, and use `Base.Slice{Base.OneTo{Int}}` axes, which preserve this information. This, however, departs from the current approach of preserving the `parentindices` of the `SubArray`. Edit: the alternate approach is implemented now.
1 parent 497deea commit 73f7acd

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

src/OffsetArrays.jl

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -693,19 +693,60 @@ julia> A
693693
no_offset_view(A::OffsetArray) = no_offset_view(parent(A))
694694
if isdefined(Base, :IdentityUnitRange)
695695
# valid only if Slice is distinguished from IdentityUnitRange
696-
no_offset_view(a::Base.Slice{<:Base.OneTo}) = a
697-
no_offset_view(a::Base.Slice) = Base.Slice(UnitRange(a))
698-
no_offset_view(S::SubArray) = view(parent(S), map(no_offset_view, parentindices(S))...)
696+
_onebasedslice(S::Base.Slice) = Base.Slice(Base.OneTo(length(S)))
697+
_onebasedslice(S::Base.Slice{<:Base.OneTo}) = S
698+
_onebasedslice(S) = S
699+
_isoffsetslice(::Any) = false
700+
_isoffsetslice(::Base.Slice) = true
701+
_isoffsetslice(::Base.Slice{<:Base.OneTo}) = false
702+
function no_offset_view(S::SubArray)
703+
#= If a view contains an offset Slice axis,
704+
i.e. it is a view of an offset array along the offset axis,
705+
we shift the axis to a 1-based one.
706+
E.g. Slice(2:3) -> Slice(Base.OneTo(2))
707+
We transform the `parent` as well as the `parentindices`,
708+
so that the view still points to the same elements, even though the indices have changed.
709+
This way, we retain the axis of the view as a `Slice`
710+
=#
711+
P = parent(S)
712+
pinds = parentindices(S)
713+
#=
714+
Check if all the axes are `Slice`s and the parent has `OneTo` axes,
715+
in which case we may unwrap the `OffsetArray` and forward the view to the parent.
716+
=#
717+
may_pop_parent = all(_isoffsetslice, pinds) && P isa OffsetArray && all(x -> x isa Base.OneTo, axes(parent(P)))
718+
if may_pop_parent
719+
return no_offset_view(P)
720+
end
721+
#=
722+
we convert offset `Slice`s to 1-based ones using `_onebasedslice`.
723+
The next call, `no_offset_view`, is a no-op on a `Slice{<:OneTo}`,
724+
while it converts the offset axes to 1-based ones.
725+
Eventually, we end up with a `Tuple` comprising `Slice{<:OneTo}`s and other 1-based axes.
726+
727+
The difference between `_onebasedslice` and `no_offset_view` is that
728+
the latter does not change the value of the range, while the former does.
729+
=#
730+
newviewinds = map(no_offset_view _onebasedslice, pinds)
731+
needs_shifting = any(_isoffsetslice, pinds)
732+
P_maybeshiftedinds = if needs_shifting
733+
t = Origin(parent(S)).index
734+
neworigin = ntuple(i -> _isoffsetslice(pinds[i]) ? 1 : t[i], length(t))
735+
Origin(neworigin)(P)
736+
else
737+
P
738+
end
739+
view(P_maybeshiftedinds, newviewinds...)
740+
end
699741
end
700742
no_offset_view(a::Array) = a
701743
no_offset_view(i::Number) = i
702744
no_offset_view(A::AbstractArray) = _no_offset_view(axes(A), A)
703745
_no_offset_view(::Tuple{}, A::AbstractArray{T,0}) where T = A
704746
_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractArray) = A
705-
# the following method is needed for ambiguity resolution
706-
_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractUnitRange) = A
707-
_no_offset_view(::Any, A::AbstractArray) = OffsetArray(A, Origin(1))
708-
_no_offset_view(::Any, A::AbstractUnitRange) = UnitRange(A)
747+
_no_offset_view(::Any, A::AbstractArray) = _no_offset_view(A)
748+
_no_offset_view(A::AbstractArray) = OffsetArray(A, Origin(1))
749+
_no_offset_view(A::AbstractUnitRange) = UnitRange(A)
709750

710751
#####
711752
# center/centered

test/runtests.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,12 @@ Base.getindex(x::PointlessWrapper, i...) = x.parent[i...]
23672367
@test OffsetArrays.no_offset_view(V) == collect(V)
23682368
V = @view O[:,:]
23692369
@test IndexStyle(A) == IndexStyle(O) == IndexStyle(V) == IndexStyle(OffsetArrays.no_offset_view(V)) == IndexLinear()
2370+
2371+
@testset "issue #375" begin
2372+
arr = OffsetArray(reshape(1:15, 3, 5), 2, 3)
2373+
arr_no_offset = OffsetArrays.no_offset_view(@view arr[:, 4])
2374+
@test all(!Base.has_offset_axes, axes(arr_no_offset))
2375+
end
23702376
end
23712377

23722378
@testset "no nesting" begin

0 commit comments

Comments
 (0)