Skip to content

Commit 8b0879b

Browse files
committed
[WIP] add StepRange support for CartesianIndices
1 parent 8a9666a commit 8b0879b

File tree

1 file changed

+76
-50
lines changed

1 file changed

+76
-50
lines changed

base/multidimensional.jl

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,13 @@ module IteratorsMD
149149
function Base.nextind(a::AbstractArray{<:Any,N}, i::CartesianIndex{N}) where {N}
150150
iter = CartesianIndices(axes(a))
151151
# might overflow
152-
I = inc(i.I, first(iter).I, last(iter).I)
152+
I = inc(i.I, first(iter).I, step.(iter.indices), last(iter).I)
153153
return I
154154
end
155155
function Base.prevind(a::AbstractArray{<:Any,N}, i::CartesianIndex{N}) where {N}
156156
iter = CartesianIndices(axes(a))
157157
# might underflow
158-
I = dec(i.I, last(iter).I, first(iter).I)
158+
I = dec(i.I, last(iter).I, step.(iter.indices), first(iter).I)
159159
return I
160160
end
161161

@@ -169,15 +169,15 @@ module IteratorsMD
169169
# Iteration
170170
"""
171171
CartesianIndices(sz::Dims) -> R
172-
CartesianIndices((istart:istop, jstart:jstop, ...)) -> R
172+
CartesianIndices((istart:[istep:]istop, jstart:[jstep:]jstop, ...)) -> R
173173
174174
Define a region `R` spanning a multidimensional rectangular range
175175
of integer indices. These are most commonly encountered in the
176176
context of iteration, where `for I in R ... end` will return
177177
[`CartesianIndex`](@ref) indices `I` equivalent to the nested loops
178178
179-
for j = jstart:jstop
180-
for i = istart:istop
179+
for j = jstart:jstep:jstop
180+
for i = istart:istep:istop
181181
...
182182
end
183183
end
@@ -190,6 +190,10 @@ module IteratorsMD
190190
As a convenience, constructing a `CartesianIndices` from an array makes a
191191
range of its indices.
192192
193+
!!! compat "Julia 1.6"
194+
The step range method `CartesianIndices((istart:istep:istop, jstart:[jstep:]jstop, ...))`
195+
requires at least Julia 1.6.
196+
193197
# Examples
194198
```jldoctest
195199
julia> foreach(println, CartesianIndices((2, 2, 2)))
@@ -222,6 +226,15 @@ module IteratorsMD
222226
223227
julia> cartesian[4]
224228
CartesianIndex(1, 2)
229+
230+
julia> cartesian = CartesianIndices((1:2:5, 1:2))
231+
3×2 CartesianIndices{2, Tuple{StepRange{Int64, Int64}, UnitRange{Int64}}}:
232+
CartesianIndex(1, 1) CartesianIndex(1, 2)
233+
CartesianIndex(3, 1) CartesianIndex(3, 2)
234+
CartesianIndex(5, 1) CartesianIndex(5, 2)
235+
236+
julia> cartesian[2, 2]
237+
CartesianIndex(3, 2)
225238
```
226239
227240
## Broadcasting
@@ -248,29 +261,36 @@ module IteratorsMD
248261
249262
For cartesian to linear index conversion, see [`LinearIndices`](@ref).
250263
"""
251-
struct CartesianIndices{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractArray{CartesianIndex{N},N}
264+
struct CartesianIndices{N,R<:NTuple{N,OrdinalRange{Int, Int}}} <: AbstractArray{CartesianIndex{N},N}
252265
indices::R
253266
end
254267

255268
CartesianIndices(::Tuple{}) = CartesianIndices{0,typeof(())}(())
256-
CartesianIndices(inds::NTuple{N,AbstractUnitRange{<:Integer}}) where {N} =
257-
CartesianIndices(map(r->convert(AbstractUnitRange{Int}, r), inds))
269+
function CartesianIndices(inds::NTuple{N,OrdinalRange{<:Integer, <:Integer}}) where {N}
270+
indices = map(r->convert(OrdinalRange{Int, Int}, r), inds)
271+
CartesianIndices{N, typeof(indices)}(indices)
272+
end
258273

259274
CartesianIndices(index::CartesianIndex) = CartesianIndices(index.I)
260-
CartesianIndices(sz::NTuple{N,<:Integer}) where {N} = CartesianIndices(map(Base.OneTo, sz))
261-
CartesianIndices(inds::NTuple{N,Union{<:Integer,AbstractUnitRange{<:Integer}}}) where {N} =
262-
CartesianIndices(map(i->first(i):last(i), inds))
275+
CartesianIndices(inds::NTuple{N,Union{<:Integer,OrdinalRange{<:Integer}}}) where {N} =
276+
CartesianIndices(map(_range2ind, inds))
263277

264278
CartesianIndices(A::AbstractArray) = CartesianIndices(axes(A))
265279

280+
_range2ind(sz::Integer) = Base.OneTo(sz)
281+
_range2ind(ind::OrdinalRange) = ind
282+
266283
"""
267-
(:)(I::CartesianIndex, J::CartesianIndex)
284+
(:)(start::CartesianIndex, [step::CartesianIndex], stop::CartesianIndex)
268285
269-
Construct [`CartesianIndices`](@ref) from two `CartesianIndex`.
286+
Construct [`CartesianIndices`](@ref) from two `CartesianIndex` and an optional step.
270287
271288
!!! compat "Julia 1.1"
272289
This method requires at least Julia 1.1.
273290
291+
!!! compat "Julia 1.6"
292+
The step range method start:step:stop requires at least Julia 1.6.
293+
274294
# Examples
275295
```jldoctest
276296
julia> I = CartesianIndex(2,1);
@@ -281,17 +301,26 @@ module IteratorsMD
281301
2×3 CartesianIndices{2, Tuple{UnitRange{Int64}, UnitRange{Int64}}}:
282302
CartesianIndex(2, 1) CartesianIndex(2, 2) CartesianIndex(2, 3)
283303
CartesianIndex(3, 1) CartesianIndex(3, 2) CartesianIndex(3, 3)
304+
305+
julia> I:CartesianIndex(1, 2):J
306+
2×2 CartesianIndices{2, Tuple{StepRange{Int64, Int64}, StepRange{Int64, Int64}}}:
307+
CartesianIndex(2, 1) CartesianIndex(2, 3)
308+
CartesianIndex(3, 1) CartesianIndex(3, 3)
284309
```
285310
"""
286311
(:)(I::CartesianIndex{N}, J::CartesianIndex{N}) where N =
287312
CartesianIndices(map((i,j) -> i:j, Tuple(I), Tuple(J)))
313+
(:)(I::CartesianIndex{N}, S::CartesianIndex{N}, J::CartesianIndex{N}) where N =
314+
CartesianIndices(map((i,s,j) -> i:s:j, Tuple(I), Tuple(S), Tuple(J)))
288315

289316
promote_rule(::Type{CartesianIndices{N,R1}}, ::Type{CartesianIndices{N,R2}}) where {N,R1,R2} =
290317
CartesianIndices{N,Base.indices_promote_type(R1,R2)}
291318

292319
convert(::Type{Tuple{}}, R::CartesianIndices{0}) = ()
293-
convert(::Type{NTuple{N,AbstractUnitRange{Int}}}, R::CartesianIndices{N}) where {N} =
294-
R.indices
320+
for RT in (OrdinalRange{Int, Int}, StepRange{Int, Int}, AbstractUnitRange{Int})
321+
@eval convert(::Type{NTuple{N,$RT}}, R::CartesianIndices{N}) where {N} =
322+
R.indices
323+
end
295324
convert(::Type{NTuple{N,AbstractUnitRange}}, R::CartesianIndices{N}) where {N} =
296325
convert(NTuple{N,AbstractUnitRange{Int}}, R)
297326
convert(::Type{NTuple{N,UnitRange{Int}}}, R::CartesianIndices{N}) where {N} =
@@ -318,13 +347,9 @@ module IteratorsMD
318347
# AbstractArray implementation
319348
Base.axes(iter::CartesianIndices{N,R}) where {N,R} = map(Base.axes1, iter.indices)
320349
Base.IndexStyle(::Type{CartesianIndices{N,R}}) where {N,R} = IndexCartesian()
321-
@inline function Base.getindex(iter::CartesianIndices{N,<:NTuple{N,Base.OneTo}}, I::Vararg{Int, N}) where {N}
322-
@boundscheck checkbounds(iter, I...)
323-
CartesianIndex(I)
324-
end
325350
@inline function Base.getindex(iter::CartesianIndices{N,R}, I::Vararg{Int, N}) where {N,R}
326351
@boundscheck checkbounds(iter, I...)
327-
CartesianIndex(I .- first.(Base.axes1.(iter.indices)) .+ first.(iter.indices))
352+
CartesianIndex(getindex.(iter.indices, I))
328353
end
329354

330355
ndims(R::CartesianIndices) = ndims(typeof(R))
@@ -351,37 +376,39 @@ module IteratorsMD
351376
iterfirst, iterfirst
352377
end
353378
@inline function iterate(iter::CartesianIndices, state)
354-
valid, I = __inc(state.I, first(iter).I, last(iter).I)
379+
valid, I = __inc(state.I, first(iter).I, step.(iter.indices), last(iter).I)
355380
valid || return nothing
356381
return CartesianIndex(I...), CartesianIndex(I...)
357382
end
358383

359384
# increment & carry
360-
@inline function inc(state, start, stop)
361-
_, I = __inc(state, start, stop)
385+
# TODO: optimize this; it adds up about 5ns overhead
386+
@inline function inc(state, start, step, stop)
387+
_, I = __inc(state, start, step, stop)
362388
return CartesianIndex(I...)
363389
end
364390

365391
# increment post check to avoid integer overflow
366-
@inline __inc(::Tuple{}, ::Tuple{}, ::Tuple{}) = false, ()
367-
@inline function __inc(state::Tuple{Int}, start::Tuple{Int}, stop::Tuple{Int})
368-
valid = state[1] < stop[1]
369-
return valid, (state[1]+1,)
392+
@inline __inc(::Tuple{}, ::Tuple{}, ::Tuple{}, ::Tuple{}) = false, ()
393+
@inline function __inc(state::Tuple{Int}, start::Tuple{Int}, step::Tuple{Int}, stop::Tuple{Int})
394+
I = state[1] + step[1]
395+
valid = I <= stop[1]
396+
return valid, (I, )
370397
end
371398

372-
@inline function __inc(state, start, stop)
373-
if state[1] < stop[1]
374-
return true, (state[1]+1, tail(state)...)
399+
@inline function __inc(state, start, step, stop)
400+
I = state[1] + step[1]
401+
if I <= stop[1]
402+
return true, (I, tail(state)...)
375403
end
376-
valid, I = __inc(tail(state), tail(start), tail(stop))
404+
valid, I = __inc(tail(state), tail(start), tail(step), tail(stop))
377405
return valid, (start[1], I...)
378406
end
379407

380408
# 0-d cartesian ranges are special-cased to iterate once and only once
381409
iterate(iter::CartesianIndices{0}, done=false) = done ? nothing : (CartesianIndex(), true)
382410

383-
size(iter::CartesianIndices) = map(dimlength, first(iter).I, last(iter).I)
384-
dimlength(start, stop) = stop-start+1
411+
size(iter::CartesianIndices) = map(length, iter.indices)
385412

386413
length(iter::CartesianIndices) = prod(size(iter))
387414

@@ -395,11 +422,8 @@ module IteratorsMD
395422
@inline to_indices(A, inds, I::Tuple{CartesianIndices{0},Vararg{Any}}) =
396423
(first(I), to_indices(A, inds, tail(I))...)
397424

398-
@inline function in(i::CartesianIndex{N}, r::CartesianIndices{N}) where {N}
399-
_in(true, i.I, first(r).I, last(r).I)
400-
end
401-
_in(b, ::Tuple{}, ::Tuple{}, ::Tuple{}) = b
402-
@inline _in(b, i, start, stop) = _in(b & (start[1] <= i[1] <= stop[1]), tail(i), tail(start), tail(stop))
425+
@inline in(i::CartesianIndex, r::CartesianIndices) = false
426+
@inline in(i::CartesianIndex{N}, r::CartesianIndices{N}) where {N} = all(map(in, i.I, r.indices))
403427

404428
simd_outer_range(iter::CartesianIndices{0}) = iter
405429
function simd_outer_range(iter::CartesianIndices)
@@ -448,36 +472,38 @@ module IteratorsMD
448472
iterfirst, iterfirst
449473
end
450474
@inline function iterate(r::Reverse{<:CartesianIndices}, state)
451-
valid, I = __dec(state.I, last(r.itr).I, first(r.itr).I)
475+
valid, I = __dec(state.I, last(r.itr).I, step.(r.itr.indices), first(r.itr).I)
452476
valid || return nothing
453477
return CartesianIndex(I...), CartesianIndex(I...)
454478
end
455479

456480
# decrement & carry
457-
@inline function dec(state, start, stop)
458-
_, I = __dec(state, start, stop)
481+
@inline function dec(state, start, step, stop)
482+
_, I = __dec(state, start, step, stop)
459483
return CartesianIndex(I...)
460484
end
461485

462486
# decrement post check to avoid integer overflow
463-
@inline __dec(::Tuple{}, ::Tuple{}, ::Tuple{}) = false, ()
464-
@inline function __dec(state::Tuple{Int}, start::Tuple{Int}, stop::Tuple{Int})
465-
valid = state[1] > stop[1]
466-
return valid, (state[1]-1,)
487+
@inline __dec(::Tuple{}, ::Tuple{}, ::Tuple{}, ::Tuple{}) = false, ()
488+
@inline function __dec(state::Tuple{Int}, start::Tuple{Int}, step::Tuple{Int}, stop::Tuple{Int})
489+
I = state[1] - step[1]
490+
valid = I >= stop[1]
491+
return valid, (I,)
467492
end
468493

469-
@inline function __dec(state, start, stop)
470-
if state[1] > stop[1]
471-
return true, (state[1]-1, tail(state)...)
494+
@inline function __dec(state, start, step, stop)
495+
I = state[1] - step[1]
496+
if I >= stop[1]
497+
return true, (I, tail(state)...)
472498
end
473-
valid, I = __dec(tail(state), tail(start), tail(stop))
499+
valid, I = __dec(tail(state), tail(start), tail(step), tail(stop))
474500
return valid, (start[1], I...)
475501
end
476502

477503
# 0-d cartesian ranges are special-cased to iterate once and only once
478504
iterate(iter::Reverse{<:CartesianIndices{0}}, state=false) = state ? nothing : (CartesianIndex(), true)
479505

480-
Base.LinearIndices(inds::CartesianIndices{N,R}) where {N,R} = LinearIndices{N,R}(inds.indices)
506+
Base.LinearIndices(inds::CartesianIndices{N,R}) where {N,R} = LinearIndices(axes(inds))
481507

482508
# array operations
483509
Base.intersect(a::CartesianIndices{N}, b::CartesianIndices{N}) where N =

0 commit comments

Comments
 (0)