Stride Operators, (a..<b)/step and (a...b)/step

stride(from:_, by:_) is unnecessary. If you want it, set the end point to Int.min for a negative stride or Int.max for a positive stride.

For example:

for i in stride(from: 0, to: Int.min, by: -2) {
    print(i) // 0 -2 -4 -6
    if i < -5 { break }
}

@Erica_Sadun What if we made a version of this function which takes range expressions?

stride(1...10, by: 3)
6 Likes

Looks very pretty, but would be it be able to count backwards? It's unclear what stride(1...10, by: -3) should do, especially when -3 is replaced by a variable whose sign is unknown at compile time.

Here's the behavior of the existing stride functions:

let a = stride(from: 1, through: 10, by: 1)
print(Array(a)) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let b = stride(from: 1, through: 10, by: -1)
print(Array(b)) // []

let c = stride(from: 1, through: 10, by: 0) // Runtime error: Stride size must not be zero.
print(Array(c))

(It's interesting that a stride has a stride btw ...)

The Range can be reversed, that is, it can be strided by -1:

  1> (0...10).reversed()
$R0: ReversedCollection<(CountableClosedRange<Int>)> = {
  _base = 0...10
}

The function reversed() returns ReversedCollection(Base). If there were the function strided(by:), this function would return StridedCollection(Base).

let x = 0...10
Array(x.reversed().strided(by: 3)) // [10, 7, 4, 1]
Array(x.strided(by: 3).reversed()) // [9 6 3 0]

The swap between reversed() and strided(by:) changes the result, but its behavior is clear.

I don't want to be that guy, but strode is the correct term to use instead of strided :) Unless you're using strided as a term-of-art, which I don't think holds its weight since strided is usually used in the context of memory strides.

1 Like

Thank you for your correction.

@soroush and I have already pitched a similar solution for a strided(by:) method here for sequences and collections: https://forums.swift.org/t/pitch-adding-strideable-sequences. Under the proposal, ranges work like this with our current code:

for a in (1 ... 5).striding(by: 2) {
  print(a) // 1, 3, 5
}

for a in (1 ... 5).reversed().striding(by: 2) {
  print(a) // 5, 3, 1
}
1 Like

I strongly prefer a method like this one over a custom operator. All the examples I've seen so far trying to introduce a custom stride operator just look like punctuation noise to me, unfortunately, and I think that's because there's no operator in common use that already represents this construct.

The method, on the other hand, is immediately understandable to the reader, and I would wager that a strided range isn't needed so frequently that it would be onerous to have to type the slightly longer method name. This is a case where the language should optimize for the reader, not the writer.

7 Likes

... and if someone really likes to save some keystrokes with an operator, he can always define it in his own code.

I think I never needed a stride with a custom step size (and I can't remember the last time I needed a for loop with a constant step > 1 ;-)

What I want is a slice like the Python slice:
https://docs.python.org/3/library/functions.html?highlight=slice#slice

a[start:stop:step]

Swift has start..<stop instead of start:stop, but doesn't have any syntax like start:stop:step.

What about the Python-like syntax a[start:stop:step] usable only for the array subscript?

I propose .| for the stride operator symbol. It resembles a leg. :running_man: :running_woman:

a[start..<stop.|step]

Core ML framework has the MLMultiArray class. This array can be sliced with strides. For example:

import CoreML
var array1 = try MLMultiArray(shape: [10], dataType: .int32)
for i in 0..<10 { array1[i] = Int32(i) as NSNumber }
print(array1)
// Int32 10 vector
// [0,1,2,3,4,5,6,7,8,9]
var view1 = try MLMultiArray(dataPointer: array1.dataPointer.advanced(by: 2*MemoryLayout<Int32>.stride), shape: [4], dataType: .int32, strides: [2])
print(view1)
// Int32 4 vector
// [2,4,6,8]
for i in 0..<view1.count { view1[i] = view1[i].int32Value * view1[i].int32Value as NSNumber }
print(array1)
// Int32 10 vector
// [0,1,4,3,16,5,36,7,64,9]

If the strided range expression could be used in the subscript of this array, the code would be simple.

A simple implementation for ArraySlice with the strided range:

I revised the ArraySliceWithStride structure, and added comparison between NumPy and ArraySliceWithStride .

I found @Chris_Lattner3 mentions striding operators in the white paper on Python API interoperability.

Swift Operators for Python.slice makes it easy to create Python slices for the NumPy array.

// NumPy Example
let np = Python.import("numpy")
let a = np.arange.call(with: 10)

let view1 = a[0.~ .| 2] // or a[.|2]
print(view1) // [0 2 4 6 8]

let view2 = a[.~8 .| 2]
print(view2) // [0 2 4 6]

let view3 = a[(-2).~1 .| -1]
print(view3) // [8 7 6 5 4 3 2]

a[.|2] = np.arange.call(with: 10, 15)
print(a) // [10  1 11  3 12  5 13  7 14  9]

This code needs Swift for TensorFlow.

That would (unnecessarily) compare the current value against the bound Int.min in each iteration.

You are right. Who want to avoid the unnecessary comparison should implement stride(from:_, by:_) into Stride.swift.gyb.