Playing with Strides

Some stride-related discussions took place on the forums today. I was inspired to play with strides as I took a break between my kids’ appointments. If any of this resonates with you as a potential user for it, let me know.

Index Strides

The following extension allows you to stride over a collection’s indices.

extension BidirectionalCollection where Index: Strideable {
    /// Returns the sequence of collection elements (`self[start]`,
    /// `self[start + stride]`, `self[start + 2 * stride]` ... *last*
    /// where *last* is the last element in the collection whose index
    /// is less than or equal to `end`.
    ///
    /// - Note: There is no guarantee that the element at index `end`
    ///   is part of the sequence
    ///
    /// - Parameter start: The starting index
    /// - Parameter end: The upper index bound
    /// - Parameter distance: The stride for each successive index
    /// - Returns: A lazy sequence of collection members
    public func stride(from start: Index, through end: Index, by distance: Index.Stride) -> LazyMapSequence<StrideThrough<Index>, Element> {
        return Swift.stride(from: start, through: end, by: distance)
            .lazy.map({ self[$0] })
    }
    
    /// Returns the sequence of collection elements (`self[start]`,
    /// `self[start + stride]`, `self[start + 2 * stride]` ... *last*
    /// where *last* is the last element in the collection whose index
    /// is strictly less than `end`.
    ///
    /// - Parameter start: The starting index
    /// - Parameter end: The upper index bound
    /// - Parameter distance: The stride for each successive index
    /// - Returns: A lazy sequence of collection members
    public func stride(from start: Index, to end: Index, by distance: Index.Stride) -> LazyMapSequence<StrideTo<Index>, Element> {
        return Swift.stride(from: start, to: end, by: distance)
            .lazy.map({ self[$0] })
    }
}

Here’s an example that uses this approach to form a sequence that strides through the collection:

let myArray = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
for value in myArray.stride(from: 0, through: myArray.count, by: 3) {
    print(value) // a, d, g, j
}

Range Operators

The next snippet uses the division operator to create its stride. This was pitched by “Tony Y” in a forum thread.

extension CountableClosedRange {
    static func / (range: CountableClosedRange, divisor: Bound.Stride) -> StrideThrough<Bound> {
        let distance = range.distance(from: range.startIndex, to: range.endIndex) / divisor // deprecated
        return Swift.stride(from: range.lowerBound, through: range.upperBound, by: distance)
    }
}

In this example, you just “divide” the range:

for idx in (0 ... 100) / 5 {
    print(idx) // 0, 20, 40, 60, 80, 100
    // yes that's 6 not 5, but isn't that what 
    // you want? 5 subranges between each index?
}

Because I wrote this fairly quickly, I used the deprecated range.distance function as straight index arithmetic seemed to error:

error: binary operator '-' cannot be applied to two 'ClosedRangeIndex' operands
        let distance = (range.endIndex - range.startIndex) / divisor

As usual, I probably messed up somewhere (or several somewheres) or you might see ways I could improve the code. Let me know!

Update: After some thought, I decided I preferred a sequence of subranges (which should probably be a type and not like I do it here):

extension CountableClosedRange {
    static func / (range: CountableClosedRange, divisor: Bound.Stride) -> UnfoldSequence<CountableClosedRange<Bound>, (CountableClosedRange<Bound>?, Bool)> {
        let distance = range.distance(from: range.startIndex, to: range.endIndex) / divisor
        let rangeStride = Swift.stride(from: range.lowerBound, through: range.upperBound, by: distance)
        var indices = Array(rangeStride)
        indices.append(range.upperBound)
        indices.reverse()
        guard var current = indices.popLast(), var next = indices.popLast() else {
            fatalError("Guaranteed count failed in index population")
        }
        var done = false
        return sequence(first: current ... next.advanced(by: -1), next: { _ in
            guard !done else { return nil }
            (current, next) = (next, indices.popLast()!)
            if current == next {
                done = true
                return next ... next
            } else if next == range.upperBound && indices.isEmpty  {
                done = true
                return current ... next
            } else {
                return current ... next.advanced(by: -1)
            }
        })
    }
}

for idx in (0 ... 99) / 5 {
    print(idx) // 0...19, 20...39, 40...59, 60...79, 80...99
}

for idx in (0 ... 100) / 5 {
    print(idx) // 0...19, 20...39, 40...59, 60...79, 80...99, 100...100
}

This isn’t a debugged solution. For example, trying to divide 1 … 1 by 1 or 1 … 2 by 1 will both fail.

3 Comments

  • Lovely, as usual. Thank you, Erica!

    Where you are striding through a collection (as in the first example), it is important to use the `to` method since the `through` will crash where the count of elements is a multiple of the `stride`:

    “`swift
    let myArray = [1, 2, 3]
    for _ in myArray.stride(from: 0, through: 3, by: 3) {} // Fatal error: Index out of range
    “`

    When I implemented `stride` method on `RandomAccessCollection` for our team, I took this into account and arrived at this api:

    “`swift
    let ints = 1…100
    ints.prefix(9).stride(by: 3) // → [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    ints.prefix(9).stride(by: 3, spanning: 2) // → [[1, 2], [4, 5], [7, 8]]
    “`

    In my case, this method returns a lazy `StridingSubsequence<Slice<CountableClosedRange>>`.

  • Forgot to mention, I also have:


    ints.prefix(4).strideAround(by: 1, spanning: 3) // [[1, 2, 3], [2, 3, 4], [3, 4, 1], [4, 1, 2]]

    ... although this would be more useful as an infinite sequence...

  • Soroush and I worked on this for a bit and here’s what we came up with: Adding Strideable Sequences