diff --git a/docs/src/index.md b/docs/src/index.md index 79004fb4..1e92fe43 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -100,7 +100,7 @@ The coefficients of this polynomial are a naturally `-1` based list, since the ` (counting from `-1`) `6, 5, -2, 3, 1` is the coefficient corresponding to the `n`th power of `x`. This Laurent polynomial can be evaluated at say `x = 2` as follows. ```jldoctest; setup = :(using OffsetArrays) -julia> coeffs = OffsetVector([6, 5, -2, 3, 1], -1:3) +julia> coeffs = OffsetVector(Int64[6, 5, -2, 3, 1], -1:3) 5-element OffsetArray(::Vector{Int64}, -1:3) with eltype Int64 with indices -1:3: 6 5 diff --git a/docs/src/internals.md b/docs/src/internals.md index c4c1173a..70023222 100644 --- a/docs/src/internals.md +++ b/docs/src/internals.md @@ -18,7 +18,7 @@ an `OffsetArray` is just a wrapper around a "parent" array, together with an index offset: ```jldoctest oa; setup=:(using OffsetArrays) -julia> oa = OffsetArray([1 2; 3 4], 0:1, 5:6) +julia> oa = OffsetArray(Int64[1 2; 3 4], 0:1, 5:6) 2×2 OffsetArray(::Matrix{Int64}, 0:1, 5:6) with eltype Int64 with indices 0:1×5:6: 1 2 3 4 @@ -44,14 +44,18 @@ type: ```jldoctest oa julia> ax = axes(oa, 2) -OffsetArrays.IdOffsetRange(5:6) +OffsetArrays.IdOffsetRange(1:2, 4) + +julia> ax == 5:6 +true ``` -This has a similar design to `Base.IdentityUnitRange` that `ax[x] == x` always holds. +This has a similar design to `Base.IdentityUnitRange`, and `ax[x] == x` always holds if the parent array follows a 1-based indexing scheme (as in this example). ```jldoctest oa julia> ax[5] 5 + julia> ax[1] ERROR: BoundsError: attempt to access 2-element Base.OneTo{Int64} at index [-3] [...] @@ -61,10 +65,16 @@ This property makes sure that they tend to be their own axes: ```jldoctest oa julia> axes(ax) -(OffsetArrays.IdOffsetRange(5:6),) +(OffsetArrays.IdOffsetRange(1:2, 4),) + +julia> axes(ax,1) == ax == 5:6 +true julia> axes(ax[ax]) -(OffsetArrays.IdOffsetRange(5:6),) +(OffsetArrays.IdOffsetRange(1:2, 4),) + +julia> axes(ax[ax], 1) == ax == 5:6 +true ``` This example of indexing is [idempotent](https://en.wikipedia.org/wiki/Idempotence). @@ -80,7 +90,7 @@ julia> oa2 = OffsetArray([5, 10, 15, 20], 0:3) 20 julia> ax2 = axes(oa2, 1) -OffsetArrays.IdOffsetRange(0:3) +OffsetArrays.IdOffsetRange(1:4, -1) julia> oa2[2] 15 @@ -109,19 +119,19 @@ julia> oa2[ax2[2]] Because `IdOffsetRange` behaves quite differently to the normal `UnitRange` type, there are some cases that you should be aware of, especially when you are working with multi-dimensional arrays. -One such cases is `getindex`: +One such cases is indexing with an `AbstractVector`, where the key fact to remember is that `axes(a[b]) == axes(b)`: ```jldoctest getindex; setup = :(using OffsetArrays) julia> Ao = zeros(-3:3, -3:3); Ao[:] .= 1:49; julia> Ao[-3:0, :] |> axes # the first dimension does not preserve offsets -(OffsetArrays.IdOffsetRange(1:4), OffsetArrays.IdOffsetRange(-3:3)) +(OffsetArrays.IdOffsetRange(1:4, 0), OffsetArrays.IdOffsetRange(1:7, -4)) julia> Ao[-3:0, -3:3] |> axes # neither dimensions preserve offsets (Base.OneTo(4), Base.OneTo(7)) julia> Ao[axes(Ao)...] |> axes # offsets are preserved -(OffsetArrays.IdOffsetRange(-3:3), OffsetArrays.IdOffsetRange(-3:3)) +(OffsetArrays.IdOffsetRange(1:7, -4), OffsetArrays.IdOffsetRange(1:7, -4)) julia> Ao[:] |> axes # This is linear indexing (Base.OneTo(49),) @@ -129,21 +139,52 @@ julia> Ao[:] |> axes # This is linear indexing Note that if you pass a `UnitRange`, the offsets in corresponding dimension will not be preserved. This might look weird at first, but since it follows the `a[ax][i] == a[ax[i]]` rule, it is not a -bug. +bug. Indexing returns a new array that is filled with values from the parent but has the axes of the indices. ```jldoctest getindex julia> I = -3:0; # UnitRange always starts at index 1 +julia> axes(Ao[I, 0]) == axes(I) +true + julia> Ao[I, 0][1] == Ao[I[1], 0] true -julia> ax = axes(Ao, 1) # ax starts at index -3 -OffsetArrays.IdOffsetRange(-3:3) +julia> ax = axes(Ao, 1) +OffsetArrays.IdOffsetRange(1:7, -4) julia> Ao[ax, 0][1] == Ao[ax[1], 0] true ``` +This might lead to behavior that isn't intuitive at first glance: + +```jldoctest setindex +julia> a = zeros(3:4, 3:4); + +julia> ax = axes(a, 1) +OffsetArrays.IdOffsetRange(1:2, 2) + +julia> ax == 3:4 +true + +julia> a[3:4, 4] .= 3:4; # works + +julia> a[ax, 4] .= 3:4; # doesn't work +ERROR: DimensionMismatch("array could not be broadcast to match destination") +[...] +``` + +The reason this assignment doesn't work is that while `axes(a, 1)` and `3:4` are equal in value, they have different axes: + +```jldoctest setindex +julia> axes(ax, 1) == 3:4 +true + +julia> axes(3:4, 1) == 1:2 +true +``` + ## Using custom axis types While a wide variety of `AbstractUnitRange`s provided by `Base` may be used as indices to construct an `OffsetArray`, at times it might be convenient to define custom types. The `OffsetArray` constructor accepts any type that may be converted to an `AbstractUnitRange`. This proceeds through a two-step process. Let's assume that the constructor called is `OffsetArray(A, indstup)`, where `indstup` is a `Tuple` of indices. @@ -163,8 +204,8 @@ julia> a = zeros(3, 3); julia> oa = OffsetArray(a, ZeroBasedIndexing()); -julia> axes(oa) -(OffsetArrays.IdOffsetRange(0:2), OffsetArrays.IdOffsetRange(0:2)) +julia> axes(oa) == (0:2, 0:2) +true ``` In this example we had to define the action of `to_indices` as the type `ZeroBasedIndexing` did not have a familiar hierarchy. Things are even simpler if we subtype `AbstractUnitRange`, in which case we need to define `first` and `length` for the custom range to be able to use it as an axis: @@ -181,8 +222,8 @@ julia> Base.length(r::ZeroTo) = r.n + 1 julia> oa = OffsetArray(zeros(2,2), ZeroTo(1), ZeroTo(1)); -julia> axes(oa) -(OffsetArrays.IdOffsetRange(0:1), OffsetArrays.IdOffsetRange(0:1)) +julia> axes(oa) == (0:1, 0:1) +true ``` Note that zero-based indexing may also be achieved using the pre-defined type [`OffsetArrays.Origin`](@ref). \ No newline at end of file diff --git a/src/axes.jl b/src/axes.jl index d5e168e0..10fffd29 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -1,5 +1,5 @@ """ - ro = IdOffsetRange(r::AbstractUnitRange, offset=0) + ro = IdOffsetRange(r::AbstractUnitRange, offset = 0) Construct an "identity offset range". Numerically, `collect(ro) == collect(r) .+ offset`, with the additional property that `axes(ro, 1) = axes(r, 1) .+ offset`. @@ -11,10 +11,13 @@ i.e., it's the "identity," which is the origin of the "Id" in `IdOffsetRange`. The most common case is shifting a range that starts at 1 (either `1:n` or `Base.OneTo(n)`): ```jldoctest; setup=:(import OffsetArrays) julia> ro = OffsetArrays.IdOffsetRange(1:3, -2) -OffsetArrays.IdOffsetRange(-1:1) +OffsetArrays.IdOffsetRange(1:3, -2) julia> axes(ro, 1) -OffsetArrays.IdOffsetRange(-1:1) +OffsetArrays.IdOffsetRange(1:3, -2) + +julia> axes(ro, 1) == -1:1 +true julia> ro[-1] -1 @@ -26,10 +29,10 @@ ERROR: BoundsError: attempt to access 3-element UnitRange{$Int} at index [5] If the range doesn't start at 1, the values may be different from the indices: ```jldoctest; setup=:(import OffsetArrays) julia> ro = OffsetArrays.IdOffsetRange(11:13, -2) -OffsetArrays.IdOffsetRange(9:11) +OffsetArrays.IdOffsetRange(11:13, -2) julia> axes(ro, 1) # 11:13 is indexed by 1:3, and the offset is also applied to the axes -OffsetArrays.IdOffsetRange(-1:1) +OffsetArrays.IdOffsetRange(1:3, -2) julia> ro[-1] 9 @@ -174,7 +177,7 @@ Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdO Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange{T}) where T = IdOffsetRange{T}(x .+ r.parent, r.offset) -Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(",first(r), ':', last(r),")") +Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(",UnitRange(r.parent), ", ", r.offset,")") # Optimizations @inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset) diff --git a/test/runtests.jl b/test/runtests.jl index 91571549..d09fbfea 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -977,9 +977,9 @@ end a = OffsetArray([1 2; 3 4], -1:0, 5:6) io = IOBuffer() show(io, axes(a, 1)) - @test String(take!(io)) == "OffsetArrays.IdOffsetRange(-1:0)" + @test String(take!(io)) == "OffsetArrays.IdOffsetRange(1:2, -2)" show(io, axes(a, 2)) - @test String(take!(io)) == "OffsetArrays.IdOffsetRange(5:6)" + @test String(take!(io)) == "OffsetArrays.IdOffsetRange(1:2, 4)" @test Base.inds2string(axes(a)) == Base.inds2string(map(UnitRange, axes(a)))