Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 57 additions & 16 deletions docs/src/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
[...]
Expand All @@ -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).
Expand All @@ -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
Expand Down Expand Up @@ -109,41 +119,72 @@ 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),)
```

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.
Expand All @@ -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:
Expand All @@ -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).
15 changes: 9 additions & 6 deletions src/axes.jl
Original file line number Diff line number Diff line change
@@ -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`.
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down