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

If a Slice instance with stride is used, we can save memory when performing forEach, map, or etc.

x[stride(from: 0, to: 100000, by: 2)].forEach {...}
let y = x[stride(from: 0, to: 100000, by: 2)].map {...}

.+= or ..+= might be better than ..+ and .++.

for x in -10 ..+= 4 {
    print(x)
    break
}
Array(10 ..+= -4 ... -10)
Array(-10 ..+= 4 ..! 10)

I wonder how many readers reacted on the fact that your statement is making the invalid assumption that "any sequence" is multi-pass, breaking the (current) semantic requirements of Sequence.

This is a very common mistake (that I myself use(d) to make). After I realized this, I started this thread which might be an Interesting read.

EDIT: No, I was wrong, your subscript will work as expected even with a single-pass sequence.

1 Like

Is it? It appears to only be doing a single pass over seq to me (the for-in loop). What am I missing?

1 Like

You are right, I misread the code (It's almost as if I want to see everyone making these mistakes! :blush: ). Edited my post.

As @Chris_Lattner3 said for the / operator, the % operator would also be not preferable.

Would it be possible to just use a contextual keyword "by" for this?

let x = 0...100 by 5

It feels very natural to me.

2 Likes

I think the keyword by should be the last option.

%% might be an option for the stride operator.

for i in -10 %% 4 {...}
for i in -10 %% 4 ..! 10 {...}

If you try the %% operator, replace the corresponding part of the previous implementation with the following code:

precedencegroup StrideFormationPrecedence {
    higherThan: RangeFormationPrecedence
    lowerThan: AdditionPrecedence
}

infix operator %% : StrideFormationPrecedence
infix operator ..! : RangeFormationPrecedence

func %% <T: Strideable>(start: T, step: T.Stride) -> Stride<T> {
    return stride(from: start, by: step)
}
for i in -10 %% 4 ... {...}

is beter than it, because the range expression ... is included. But, I cloud not implement this postfix operator .... Please help me!

I implemented the stride operator %% to create StrideTo from Range, StrideThrough from ClosedRange, and StrideFrom from CountablePartialRangeFrom, PartialRangeUpTo, and PartialRangeThrough. The StrideFrom structure is almost the same as the Stride structure introduced by @Alexandre_Lopoukhine.

For example,

Array(-10..<10%%4) // [-10, -6, -2, 2, 6]
Array(-10...10%%4) // [-10, -6, -2, 2, 6, 10]
for i in (-10)... %% 4 {
  print(i) // -10 -6 -2 2 6 10
  if i >= 10 { break }
}
for i in ..<10 %% -4 {
  print(i) // 6 2 -2 -6 -10
  if i <= -10 { break }
}
for i in ...10 %% -4 {
  print(i) // 10 6 2 -2 -6 -10
  if i <= -10 { break }
}

I spent some time playing around with this. I found that it feels less readable than using stride directly, for example:

// Clear, understandable, built using recognition
stride(from: 1, through: 10, by: 3)

// Depends more on interpreting intent
(1 ... 10) / 3
// Does this return (1/3, 2/3, 3/3, ...)?
// Does this return 1, 4, 7?
// Does this return 1..<4, 4..<7, 7..<10? or 1..<4, 4..<7, 7..<10, 10...10?

The answer is the first (because you state it wraps stride) but in doing so, it removes clarity to achieve concision and forces the use of parentheses.

Thank you for bringing up this topic. It gave me a lot to think about.

7 Likes

I fixed the %% operator for the half-open range stride with a negative stride.

-        return stride(from: left.upperBound, to: left.lowerBound, by: right)
+        return stride(from: left.upperBound.advanced(by: right), to: left.lowerBound.advanced(by: right), by: right)

Upon this fix, the reverse stride of the half-open range behaves as you expect:

Array(-10..<10 %% -4) // [6, 2, -2, -6, -10]

The generic structure Array has the instance property:

  var indices: CountableRange<Int> { get }

To create the stride range from this indices, I added the stride operators %% for CountableRange as well as CountableClosedRange .

Github link

For example:

let a = (0..<100).map { Double($0) }
for i in a.indices %% 20 { print(a[i]) } // 0.0 20.0 40.0 60.0 80.0
for i in a.indices %% -20 { print(a[i]) } // 80.0 60.0 40.0 20.0 0.0
let a = (0...100).map { Double($0) }
let range = CountableClosedRange(a.indices)
for i in range %% 20 { print(a[i]) } // 0.0 20.0 40.0 60.0 80.0 100.0
for i in range %% -20 { print(a[i]) } // 100.0 80.0 60.0 40.0 20.0 0.0

I am sorry. This fix is wrong. I fixed as the stride starts from the last element of the half-open range:

-        return stride(from: left.upperBound.advanced(by: right), to: left.lowerBound.advanced(by: right), by: right)
+        return stride(from: left.upperBound.advanced(by: -1), to: left.lowerBound.advanced(by: -1), by: right)

Github link

For example:

Array(-10..<10 %% -4) // [9, 5, 1, -3, -7]
let a = (0..<100).map { Double($0) }
for i in a.indices %% -20 {
  print(a[i]) // 99.0 79.0 59.0 39.0 19.0
}

For the PartialRangeUpTo range, I also fixed as the stride starts from the last element of the range:

-        return stride(from: left.upperBound.advanced(by: right), by: right)
+        return stride(from: left.upperBound.advanced(by: -1), by: right)

Github link

For example:

for i in ..<10 %% -4 {
   print(i) // 9 5 1 -3 -7 -11
   if i <= -10 { break }
}

Hi @Tony-Y – you shouldn't need to worry about CountableRange any more. That type was a workaround from before we had the ability to express that Range was a Collection when its Bound was Strideable. Now that Swift has conditional conformance, the CountableRange type is just a typealias for Range.

Thank you for pointing it out.

This message of the deprecated structure CountableClosedRange should be "CountableClosedRange is now ClosedRange. ...".