diff --git a/src/OffsetArrays.jl b/src/OffsetArrays.jl index 0e5c19ee..e91be0e4 100644 --- a/src/OffsetArrays.jl +++ b/src/OffsetArrays.jl @@ -47,6 +47,7 @@ OffsetVector{T}(init::ArrayInitializer, inds::AbstractUnitRange) where {T} = Off # OffsetMatrix constructors OffsetMatrix(A::AbstractMatrix, offset1, offset2) = OffsetArray(A, offset1, offset2) +OffsetMatrix(A::AbstractMatrix, I::CartesianIndices{2}) = OffsetArray(A, I) OffsetMatrix{T}(init::ArrayInitializer, inds1::AbstractUnitRange, inds2::AbstractUnitRange) where {T} = OffsetArray{T}(init, inds1, inds2) """ @@ -82,12 +83,63 @@ uncolonindices(ax::Tuple, inds::Tuple) = (first(inds), uncolonindices(tail(ax), uncolonindices(ax::Tuple, inds::Tuple{Colon, Vararg{Any}}) = (first(ax), uncolonindices(tail(ax), tail(inds))...) uncolonindices(::Tuple{}, ::Tuple{}) = () -function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,Union{AbstractUnitRange, Colon}}) where {T,N} +function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,Union{AbstractUnitRange, CartesianIndices{1}, Colon}}) where {T,N} OffsetArray(A, uncolonindices(A, inds)) end -OffsetArray(A::AbstractArray{T,N}, inds::Vararg{Union{AbstractUnitRange, Colon},N}) where {T,N} = +OffsetArray(A::AbstractArray{T,N}, inds::Vararg{Union{AbstractUnitRange, CartesianIndices{1}, Colon},N}) where {T,N} = OffsetArray(A, uncolonindices(A, inds)) +# Specify offsets using CartesianIndices (issue #71) +# Support a mix of AbstractUnitRanges and CartesianIndices{1} +# Extract the range r from CartesianIndices((r,)) +function stripCartesianIndices(inds::Tuple{CartesianIndices{1},Vararg{Any}}) + I = first(inds) + Ir = convert(Tuple{AbstractUnitRange{Int}}, I) |> first + (Ir, stripCartesianIndices(tail(inds))...) +end +stripCartesianIndices(inds::Tuple)= (first(inds), stripCartesianIndices(tail(inds))...) +stripCartesianIndices(::Tuple{}) = () + +OffsetArray(A::AbstractArray{<:Any,N}, inds::NTuple{N,Union{CartesianIndices{1}, AbstractUnitRange}}) where {N} = + OffsetArray(A, stripCartesianIndices(inds)) +OffsetArray(A::AbstractArray{<:Any,N}, inds::Vararg{Union{CartesianIndices{1}, AbstractUnitRange},N}) where {N} = + OffsetArray(A, inds) + +# Support an arbitrary CartesianIndices alongside colons and ranges +# The total number of indices should equal ndims(arr) +# We split the CartesianIndices{N} into N CartesianIndices{1} indices to facilitate dispatch +splitCartesianIndices(c::CartesianIndices{0}) = () +function splitCartesianIndices(c::CartesianIndices) + c1, ct = Base.IteratorsMD.split(c, Val(1)) + (c1, splitCartesianIndices(ct)...) +end +function splitCartesianIndices(t::Tuple{CartesianIndices, Vararg{Any}}) + (splitCartesianIndices(first(t))..., splitCartesianIndices(tail(t))...) +end +function splitCartesianIndices(t::Tuple) + (first(t), splitCartesianIndices(tail(t))...) +end +splitCartesianIndices(::Tuple{}) = () + +function OffsetArray(A::AbstractArray, inds::Tuple{Vararg{Union{AbstractUnitRange, CartesianIndices, Colon}}}) + OffsetArray(A, splitCartesianIndices(inds)) +end +function OffsetArray(A::AbstractArray, inds::Vararg{Union{AbstractUnitRange, CartesianIndices, Colon}}) + OffsetArray(A, inds) +end + +# Add methods to initialize OffsetArrays using CartesianIndices (issue #71) +function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{Union{AbstractUnitRange, CartesianIndices}}}) where {T,N} + indsN = stripCartesianIndices(splitCartesianIndices(inds)) + OffsetArray{T,N}(init, indsN) +end +function OffsetArray{T}(init::ArrayInitializer, inds::Tuple{Vararg{Union{AbstractUnitRange, CartesianIndices}}}) where {T} + indsN = stripCartesianIndices(splitCartesianIndices(inds)) + OffsetArray{T}(init, indsN) +end +OffsetArray{T,N}(init::ArrayInitializer, inds::Vararg{Union{AbstractUnitRange, CartesianIndices}}) where {T,N} = OffsetArray{T,N}(init, inds) +OffsetArray{T}(init::ArrayInitializer, inds::Vararg{Union{AbstractUnitRange, CartesianIndices}}) where {T} = OffsetArray{T}(init, inds) + # avoid a level of indirection when nesting OffsetArrays function OffsetArray(A::OffsetArray, offsets::NTuple{N,Int}) where {N} OffsetArray(parent(A), offsets .+ A.offsets) @@ -247,21 +299,19 @@ indexlength(r::AbstractRange) = length(r) indexlength(i::Integer) = i indexlength(i::Colon) = Colon() +# These functions keep the summary compact +function Base.inds2string(inds::Tuple{Vararg{Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}}}}) + Base.inds2string(map(UnitRange, inds)) +end +Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...) + function Base.showarg(io::IO, a::OffsetArray, toplevel) print(io, "OffsetArray(") Base.showarg(io, parent(a), false) - if ndims(a) > 0 - print(io, ", ") - printindices(io, axes(a)...) - end + Base.showindices(io, axes(a)...) print(io, ')') toplevel && print(io, " with eltype ", eltype(a)) end -printindices(io::IO, ind1, inds...) = - (print(io, _unslice(ind1), ", "); printindices(io, inds...)) -printindices(io::IO, ind1) = print(io, _unslice(ind1)) -_unslice(x) = x -_unslice(x::IdentityUnitRange) = x.indices function Base.replace_in_print_matrix(A::OffsetArray{<:Any,2}, i::Integer, j::Integer, s::AbstractString) J = map(parentindex, axes(A), (i,j)) diff --git a/src/axes.jl b/src/axes.jl index f21693ff..19e5dd43 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -163,7 +163,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, first(r), ':', last(r)) +Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(",first(r), ':', last(r),")") # 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 ebc8e216..336a832f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -110,6 +110,15 @@ end @test OffsetVector(v, -2:2) == OffsetArray(v, -2:2) @test typeof(OffsetVector{Float64}(undef, -2:2)) == typeof(OffsetArray{Float64}(undef, -2:2)) + # Issue #71 + ov = OffsetVector(v, -2:2) + for T in [OffsetVector, OffsetArray] + if VERSION >= v"1.1" + @test ov == T(v, CartesianIndex(-2):CartesianIndex(2)) + end + @test ov == T(v, CartesianIndices((-2:2,))) + end + @test OffsetVector(v, :) == OffsetArray(v, (:,)) == OffsetArray(v, :) == OffsetArray(v, axes(v)) @test axes(OffsetVector(v, :)) == axes(v) @@ -124,6 +133,18 @@ end @test OffsetMatrix(v, -2:2, -1:1) == OffsetArray(v, -2:2, -1:1) @test typeof(OffsetMatrix{Float64}(undef, -2:2, -1:1)) == typeof(OffsetArray{Float64}(undef, -2:2, -1:1)) + # Issue #71 + m = OffsetMatrix(v, -2:2, -1:1) + for T in [OffsetMatrix, OffsetArray] + if VERSION >= v"1.1" + @test m == T(v, CartesianIndex(-2, -1):CartesianIndex(2, 1)) + @test m == T(v, CartesianIndex(-2):CartesianIndex(2), CartesianIndex(-1):CartesianIndex(1)) + @test m == T(v, -2:2, CartesianIndex(-1):CartesianIndex(1)) + @test m == T(v, CartesianIndex(-2):CartesianIndex(2), -1:1) + end + @test m == T(v, CartesianIndices((-2:2, -1:1))) + end + @test OffsetMatrix(v, :, :) == OffsetArray(v, (:, :)) == OffsetArray(v, :, :) == OffsetArray(v, axes(v)) @test OffsetMatrix(v, :, 2:4) == OffsetArray(v, axes(v,1), 2:4) @test OffsetMatrix(v, 3:7, :) == OffsetArray(v, 3:7, axes(v,2)) @@ -134,6 +155,28 @@ end @test OffsetMatrix(w, :, 2:3) == OffsetArray(w, axes(w,1), 2:3) @test OffsetMatrix(w, 0:1, :) == OffsetArray(w, 0:1, axes(w,2)) @test axes(OffsetArray(w, :, :)) == axes(w) + + @test axes(OffsetMatrix(w, :, CartesianIndices((0:1,)))) == (3:4, 0:1) + @test axes(OffsetMatrix(w, CartesianIndices((0:1,)), :)) == (0:1, 5:6) +end + +@testset "construct OffsetArray with CartesianIndices" begin + a = rand(2, 2, 2) + oa = OffsetArray(a, 0:1, 3:4, 2:3) + @test OffsetArray(a, CartesianIndices(axes(oa))) == oa + @test axes(OffsetArray(a, :, CartesianIndices((3:4, 2:3)))) == (1:2, 3:4, 2:3) + @test axes(OffsetArray(a, 10:11, CartesianIndices((3:4, 2:3)) )) == (10:11, 3:4, 2:3) + @test axes(OffsetArray(a, CartesianIndices((3:4, 2:3)), :)) == (3:4, 2:3, 1:2) + @test axes(OffsetArray(a, CartesianIndices((3:4, 2:3)), 10:11)) == (3:4, 2:3, 10:11) + @test axes(OffsetArray(a, :, :, CartesianIndices((3:4,)) )) == (1:2, 1:2, 3:4) + @test axes(OffsetArray(a, 10:11, :, CartesianIndices((3:4,)) )) == (10:11, 1:2, 3:4) + @test axes(OffsetArray(a, 10:11, 2:3, CartesianIndices((3:4,)) )) == (10:11, 2:3, 3:4) + + # ignore empty CartesianIndices + @test OffsetArray(a, CartesianIndices(()), 0:1, :, 2:3) == OffsetArray(a, 0:1, :, 2:3) + @test OffsetArray(a, 0:1, CartesianIndices(()), :, 2:3) == OffsetArray(a, 0:1, :, 2:3) + @test OffsetArray(a, 0:1, :, CartesianIndices(()), 2:3) == OffsetArray(a, 0:1, :, 2:3) + @test OffsetArray(a, 0:1, :, 2:3, CartesianIndices(())) == OffsetArray(a, 0:1, :, 2:3) end @testset "undef, missing, and nothing constructors" begin @@ -151,6 +194,25 @@ end @test !isassigned(OffsetVector{Union{T,Vector{Int}}}(undef, -1:1), -1) @test OffsetVector{Union{T,Vector{Int}}}(t, -1:1)[-1] === t end + + oa = OffsetArray{Float64,2}(undef, CartesianIndices((1:2, 3:4))) + @test axes(oa) == (1:2, 3:4) + @test eltype(oa) == Float64 + oa = OffsetArray{Float64,2}(undef, 1:2, CartesianIndices((3:4,))) + @test axes(oa) == (1:2, 3:4) + @test eltype(oa) == Float64 + oa = OffsetArray{Float64,2}(undef, (1:2, CartesianIndices((3:4,)))) + @test axes(oa) == (1:2, 3:4) + @test eltype(oa) == Float64 + oa = OffsetArray{Float64}(undef, CartesianIndices((1:2, 3:4))) + @test axes(oa) == (1:2, 3:4) + @test eltype(oa) == Float64 + oa = OffsetArray{Float64}(undef, 1:2, CartesianIndices((3:4,))) + @test axes(oa) == (1:2, 3:4) + @test eltype(oa) == Float64 + oa = OffsetArray{Float64}(undef, (1:2, CartesianIndices((3:4,)))) + @test axes(oa) == (1:2, 3:4) + @test eltype(oa) == Float64 end @testset "Offset range construction" begin @@ -440,15 +502,32 @@ end a = OffsetArray(reshape([1])) @test summary(a) == "0-dimensional OffsetArray(::$(typeof(parent(a)))) with eltype $(Int)" + 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)" + show(io, axes(a, 2)) + @test String(take!(io)) == "OffsetArrays.IdOffsetRange(5:6)" + + @test Base.inds2string(axes(a)) == Base.inds2string(map(UnitRange, axes(a))) + show(io, OffsetArray(3:5, 0:2)) @test String(take!(io)) == "3:5 with indices 0:2" d = Diagonal([1,2,3]) Base.print_array(io, d) s1 = String(take!(io)) - Base.print_array(io, OffsetArray(d, -1:1, 3:5)) + od = OffsetArray(d, -1:1, 3:5) + Base.print_array(io, od) s2 = String(take!(io)) @test s1 == s2 + + @test Base.replace_in_print_matrix(od, -1, 3, " ") == Base.replace_in_print_matrix(d, 1, 1, " ") + @test Base.replace_in_print_matrix(od, -1, 4, " ") == Base.replace_in_print_matrix(d, 1, 2, " ") + + v = rand(3) + ov = OffsetArray(v, (-2,)) + @test Base.replace_in_print_matrix(ov, -1, 1, " ") == Base.replace_in_print_matrix(v, 1, 1, " ") end @testset "readdlm/writedlm" begin