diff --git a/docs/src/index.md b/docs/src/index.md index 403748b6..455bdbe8 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -55,6 +55,15 @@ Base.require_one_based_indexing(ans) Base.require_one_based_indexing(OA) ``` +[`OffsetArrays.Origin`](@ref) can be convenient if you want to directly specify the origin of the output +OffsetArray, it will automatically compute the needed offsets. For example: + +```@repl index +OffsetArray(A, OffsetArrays.Origin(-1, -1)) +OffsetArray(OA, OffsetArrays.Origin(-1, -1)) +``` + + ## Example: Relativistic Notation Suppose we have a position vector `r = [:x, :y, :z]` which is naturally one-based, ie. `r[1] == :x`, `r[2] == :y`, `r[3] == :z` and we also want to construct a relativistic position vector which includes time as the 0th component. This can be done with OffsetArrays like diff --git a/docs/src/reference.md b/docs/src/reference.md index 6c605277..500ad5f8 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -2,6 +2,7 @@ ```@docs OffsetArray +OffsetArrays.Origin OffsetArrays.IdOffsetRange OffsetArrays.no_offset_view ``` diff --git a/src/OffsetArrays.jl b/src/OffsetArrays.jl index 6a4911e8..fd7cc562 100644 --- a/src/OffsetArrays.jl +++ b/src/OffsetArrays.jl @@ -11,6 +11,7 @@ export OffsetArray, OffsetMatrix, OffsetVector include("axes.jl") include("utils.jl") +include("origin.jl") # Technically we know the length of CartesianIndices but we need to convert it first, so here we # don't put it in OffsetAxisKnownLength. @@ -25,7 +26,7 @@ const ArrayInitializer = Union{UndefInitializer, Missing, Nothing} Return an `AbstractArray` that shares element type and size with the first argument, but used the given `indices`, which are checked for compatible size. -# Example +# Example: offsets There are two types of `indices`: integers and ranges-like types. @@ -68,6 +69,25 @@ julia> OffsetArray(reshape(1:6, 2, 3), 0, -1:1) ERROR: [...] ``` +# Example: origin + +[`OffsetArrays.Origin`](@ref) can be used to directly specify the origin of the output OffsetArray. + +```jldoctest; setup=:(using OffsetArrays) +julia> a = [1 2; 3 4]; + +julia> OffsetArray(a, OffsetArrays.Origin(0, 1)) +2×2 OffsetArray(::$(Array{Int64,2}), 0:1, 1:2) with eltype Int64 with indices 0:1×1:2: + 1 2 + 3 4 + +julia> OffsetArray(a, OffsetArrays.Origin(0)) # short notation for `Origin(0, ..., 0)` +2×2 OffsetArray(::$(Array{Int64, 2}), 0:1, 0:1) with eltype Int64 with indices 0:1×0:1: + 1 2 + 3 4 +``` + + """ struct OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N} parent::AA @@ -115,6 +135,8 @@ for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix) $FT(A, indsN) end @eval $FT(A::AbstractArray{T}, inds::Vararg{OffsetAxis,N}) where {T, N} = $FT(A, inds) + + @eval $FT(A::AbstractArray, origin::Origin) = OffsetArray(A, origin(A)) end # array initialization diff --git a/src/origin.jl b/src/origin.jl new file mode 100644 index 00000000..9aa21d81 --- /dev/null +++ b/src/origin.jl @@ -0,0 +1,35 @@ +""" + Origin(indices...) + Origin(origin::Tuple) + Origin(origin::CartesianIndex) + +A helper type to construct OffsetArray with given origin. + +The `origin` of an array is defined as the index of its first element, i.e., `first.(axes(A))`. + +# Example + +```jldoctest; setup=:(using OffsetArrays) +julia> a = [1 2; 3 4]; + +julia> OffsetArray(a, OffsetArrays.Origin(0, 1)) +2×2 OffsetArray(::$(Array{Int64,2}), 0:1, 1:2) with eltype Int64 with indices 0:1×1:2: + 1 2 + 3 4 + +julia> OffsetArray(a, OffsetArrays.Origin(0)) # short notation for `Origin(0, 0)` +2×2 OffsetArray(::$(Array{Int64, 2}), 0:1, 0:1) with eltype Int64 with indices 0:1×0:1: + 1 2 + 3 4 +``` +""" +struct Origin{T<:Union{Tuple, Int}} + index::T +end +Origin(I::NTuple{N, Int}) where N = Origin{typeof(I)}(I) +Origin(I::CartesianIndex) = Origin(I.I) +Origin(I1::Int, In::Int...) = Origin((I1, In...)) +# Origin(0) != Origin((0, )) but they work the same with broadcasting +Origin(n::Int) = Origin{Int}(n) + +(o::Origin)(A::AbstractArray) = o.index .- first.(axes(A)) diff --git a/test/origin.jl b/test/origin.jl new file mode 100644 index 00000000..a49172cd --- /dev/null +++ b/test/origin.jl @@ -0,0 +1,30 @@ +using OffsetArrays: Origin +@testset "Origin" begin + get_origin(A::AbstractArray) = first.(axes(A)) + + @test Origin(0) != Origin((0, )) + @test Origin(CartesianIndex(1, 2)) === Origin((1, 2)) === Origin(1, 2) + + # 1d + v = [1, 2] + @test get_origin(OffsetArray(v, Origin(2))) == (2, ) + ov = OffsetArray(v, -3) + @test get_origin(OffsetArray(ov, Origin(2))) == (2, ) + @test get_origin(OffsetVector(ov, Origin(2))) == (2, ) + @test get_origin(OffsetArray(ov, Origin((2, )))) == (2, ) + + # 2d + a = [1 2;3 4] + @test get_origin(OffsetArray(a, Origin(0))) == (0, 0) + oa = OffsetArray(a, -3, -3) + @test get_origin(OffsetArray(oa, Origin(0))) == (0, 0) + @test get_origin(OffsetMatrix(oa, Origin(0))) == (0, 0) + @test get_origin(OffsetArray(oa, Origin(1, 2))) == (1, 2) + + # 3d + a = ones(3, 3, 3) + @test get_origin(OffsetArray(a, Origin(0))) == (0, 0, 0) + oa = OffsetArray(a, -3, -3, -3) + @test get_origin(OffsetArray(oa, Origin(0))) == (0, 0, 0) + @test get_origin(OffsetArray(oa, Origin(1, 2, 3))) == (1, 2, 3) +end diff --git a/test/runtests.jl b/test/runtests.jl index 306b091f..1a29cf7a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1112,3 +1112,5 @@ end @test searchsorted(o, 5) == 2:2 @test searchsorted(o, 6) == 3:2 end + +include("origin.jl")