From d0151a2df4a946c3baacd46642071565c68c4fde Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 16 Mar 2017 09:40:08 -0500 Subject: [PATCH] Fixes and more tests for Base.Slice This is in preparation for a later reparametrization and renaming --- base/abstractarray.jl | 4 +- base/indices.jl | 53 +++++++++++++++++++--- base/subarray.jl | 6 ++- test/arrayops.jl | 100 ++++++++++++++++++++++++++++++++++++++++++ test/offsetarray.jl | 7 +++ 5 files changed, 162 insertions(+), 8 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 5ec677a2db340..26ee4a78e8efd 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -440,13 +440,15 @@ false checkindex(::Type{Bool}, inds::AbstractUnitRange, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Real) = (first(inds) <= i) & (i <= last(inds)) checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Colon) = true -checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Slice) = true +checkindex(::Type{Bool}, inds::AbstractUnitRange, s::Slice) = + (first(s) >= first(inds)) & (last(s) <= last(inds)) function checkindex(::Type{Bool}, inds::AbstractUnitRange, r::Range) @_propagate_inbounds_meta isempty(r) | (checkindex(Bool, inds, first(r)) & checkindex(Bool, inds, last(r))) end checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractVector{Bool}) = indx == indices1(I) checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractArray{Bool}) = false +checkindex(::Type{Bool}, indx::Slice, I::AbstractVector{Bool}) = indx.indices == indices1(I) function checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractArray) @_inline_meta b = true diff --git a/base/indices.jl b/base/indices.jl index e01b6699592b0..e3d693d8a945f 100644 --- a/base/indices.jl +++ b/base/indices.jl @@ -218,20 +218,40 @@ to_indices(A, inds, I::Tuple{Any, Vararg{Any}}) = _maybetail(::Tuple{}) = () _maybetail(t::Tuple) = tail(t) +# TODO: reparametrize in a manner consistent with other AbstractUnitRanges, +# rename (IdempotentRange? IdentityRange?), and move to ranges.jl """ - Slice(indices) + Slice(r::AbstractUnitRange) -> s -Represent an AbstractUnitRange of indices as a vector of the indices themselves. +Construct an `AbstractUnitRange` where `s[i] == i` for any valid `i`; +equivalently, `indices(s, 1) == r` and `s[s] === s`. + +These are particularly useful for creating `view`s of arrays that +preserve the supplied indices: +```jldoctest +julia> a = rand(8); + +julia> v1 = view(a, 3:5); + +julia> indices(v1, 1) +Base.OneTo(3) + +julia> s = Base.Slice(3:5) +Base.Slice(3:5) + +julia> v2 = view(a, s); + +julia> indices(v2, 1) +3:5 +``` Upon calling `to_indices()`, Colons are converted to Slice objects to represent -the indices over which the Colon spans. Slice objects are themselves unit -ranges with the same indices as those they wrap. This means that indexing into -Slice objects with an integer always returns that exact integer, and they -iterate over all the wrapped indices, even supporting offset indices. +the indices over which the Colon spans. """ struct Slice{T<:AbstractUnitRange} <: AbstractUnitRange{Int} indices::T end +Slice(S::Slice) = S # idempotent indices(S::Slice) = (S.indices,) unsafe_indices(S::Slice) = (S.indices,) indices1(S::Slice) = S.indices @@ -242,7 +262,28 @@ size(S::Slice) = first(S.indices) == 1 ? (length(S.indices),) : errmsg(S) length(S::Slice) = first(S.indices) == 1 ? length(S.indices) : errmsg(S) unsafe_length(S::Slice) = first(S.indices) == 1 ? unsafe_length(S.indices) : errmsg(S) getindex(S::Slice, i::Int) = (@_inline_meta; @boundscheck checkbounds(S, i); i) +function getindex(r::Slice, s::AbstractUnitRange{<:Integer}) + @_inline_meta + @boundscheck checkbounds(r, s) + s +end show(io::IO, r::Slice) = print(io, "Base.Slice(", r.indices, ")") start(S::Slice) = start(S.indices) next(S::Slice, s) = next(S.indices, s) done(S::Slice, s) = done(S.indices, s) +intersect{I<:AbstractUnitRange{Int}}(r::Slice{I}, s::Slice{I}) = + Slice(convert(I, max(first(r), first(s)):min(last(r), last(s)))) +intersect(r::Slice, s::Slice) = + Slice(max(first(r), first(s)):min(last(r), last(s))) +reverse(r::Slice) = error("reverse is not supported for Base.Slice") +sortperm(r::Slice) = r +==(r::Slice, s::Slice) = (first(r) == first(s)) & (step(r) == step(s)) & (last(r) == last(s)) +==(r::Slice, s::OrdinalRange) = (first(r) == first(s) == 1) & (step(r) == step(s)) & (last(r) == last(s)) +==(s::OrdinalRange, r::Slice) = r == s +promote_rule{R1,R2}(::Type{Slice{R1}},::Type{Slice{R2}}) = + Slice{promote_type(R1,R2)} +convert{R<:AbstractUnitRange{Int}}(::Type{Slice{R}}, r::Slice{R}) = r +convert{R<:AbstractUnitRange{Int}}(::Type{Slice{R}}, r::Slice) = + Slice(convert(R, r.indices)) +convert{R<:AbstractUnitRange{Int}}(::Type{Slice}, r::Slice{R}) = + convert(Slice{R}, r) diff --git a/base/subarray.jl b/base/subarray.jl index 536c569f11920..4386ae6cbddd0 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -266,10 +266,14 @@ end compute_offset1(parent, stride1::Integer, I::Tuple) = (@_inline_meta; compute_offset1(parent, stride1, find_extended_dims(I)..., I)) compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{Slice}, I::Tuple) = - (@_inline_meta; compute_linindex(parent, I) - stride1*first(indices(parent, dims[1]))) # index-preserving case + (@_inline_meta; compute_linindex(parent, I) - stride1*first(inds[1])) compute_offset1(parent, stride1::Integer, dims, inds, I::Tuple) = (@_inline_meta; compute_linindex(parent, I) - stride1) # linear indexing starts with 1 +function compute_linindex(parent::AbstractVector, I::Tuple{Any}) + @_inline_meta + first(I[1]) +end function compute_linindex{N}(parent, I::NTuple{N,Any}) @_inline_meta IP = fill_to_length(indices(parent), OneTo(1), Val{N}) diff --git a/test/arrayops.jl b/test/arrayops.jl index 37c91750830b6..b407671de5f3b 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2071,3 +2071,103 @@ end Base.:*(a::T11053, b::Real) = T11053(a.a*b) Base.:(==)(a::T11053, b::T11053) = a.a == b.a @test [T11053(1)] * 5 == [T11053(1)] .* 5 == [T11053(5.0)] + +@testset "Slice" begin + r = Base.Slice(0:-5) + @test isempty(r) + r = Base.Slice(0:2) + @test Base.Slice(r) === r + @test !isempty(r) + @test indices(r) === (0:2,) + @test step(r) == 1 + @test first(r) == 0 + @test last(r) == 2 + @test minimum(r) == 0 + @test maximum(r) == 2 + @test r[0] == 0 + @test r[1] == 1 + @test r[2] == 2 + @test r != 0:2 + @test r == Base.Slice(0:2) + @test r === Base.Slice(0:2) + @test_throws BoundsError r[3] + @test_throws BoundsError r[-1] + @test r[0:2] === 0:2 + @test r[r] === r + @test r[Base.Slice(1:2)] === Base.Slice(1:2) + @test r[1:2] === 1:2 + @test_throws BoundsError r[Base.Slice(1:3)] + @test_throws BoundsError r[1:3] + k = -1 + for i in r + @test i == (k+=1) + end + @test k == 2 + @test intersect(r, Base.Slice(-1:1)) === intersect(Base.Slice(-1:1), r) === Base.Slice(0:1) + @test intersect(r, -1:5) === intersect(-1:5, r) === 0:2 + @test intersect(r, 2:5) === intersect(2:5, r) === 2:2 + # Not ideal, but at least this isn't wrong... + @test_throws ErrorException r+1 + @test_throws ErrorException r-1 + @test_throws ErrorException 2*r + @test_throws ErrorException r/2 + @test_throws ErrorException reverse(r) + + r = Base.Slice(2:4) + @test r != 2:4 + @test r == Base.Slice(2:4) + @test r === Base.Slice(2:4) + @test Base.Slice(1:4) == 1:4 + @test checkindex(Bool, r, 4) + @test !checkindex(Bool, r, 5) + @test checkindex(Bool, r, :) + @test checkindex(Bool, r, 2:4) + @test !checkindex(Bool, r, 1:5) + @test !checkindex(Bool, r, trues(4)) + @test !checkindex(Bool, r, trues(3)) + @test checkindex(Bool, r, view(trues(5), r)) + @test !in(1, r) + @test in(2, r) + @test in(4, r) + @test !in(5, r) + @test issorted(r) + @test maximum(r) == 4 + @test minimum(r) == 2 + @test sortperm(r) == r + + r = Base.Slice(Int16(0):Int16(4)) + @test start(r) === 0 + k = -1 + for i in r + @test i == (k+=1) + end + @test k == 4 + x, y = promote(r, Base.Slice(2:4)) + @test x === Base.Slice(0:4) + @test y === Base.Slice(2:4) + x, y = promote(Base.Slice(4:5), 0:7) + @test x === 4:5 + @test y === 0:7 + r = Base.Slice(Int128(1):Int128(10)) + @test length(r) === Int128(10) +end + +@testset "Slice with view" begin + a = rand(8) + idr = Base.Slice(2:4) + v = view(a, idr) + @test indices(v) == (2:4,) + @test v[2] == a[2] + @test v[3] == a[3] + @test v[4] == a[4] + @test_throws BoundsError v[1] + @test_throws BoundsError v[5] + + a = rand(5, 5) + idr2 = Base.Slice(3:4) + v = view(a, idr, idr2) + @test indices(v) == (2:4, 3:4) + @test v[2,3] == a[2,3] +end + +nothing diff --git a/test/offsetarray.jl b/test/offsetarray.jl index 3d9e1355b9b27..cf0d101ec24d4 100644 --- a/test/offsetarray.jl +++ b/test/offsetarray.jl @@ -84,6 +84,13 @@ for i = 1:9 @test A_3_3[i] == i end @test_throws BoundsError A[[true true; false true]] # view +x = OffsetArray(1:8, (-2,)) +S = view(x, :) +@test indices(S) == (-1:6,) +@test S[-1] == 1 +@test S[6] == 8 +@test_throws BoundsError S[-2] +@test_throws BoundsError S[7] S = view(A, :, 3) @test S == OffsetArray([1,2], (A.offsets[1],)) @test S[0] == 1