Search code examples
swiftfor-loopstride

Swift stride function, cannot modify end value


In Swift, the c-style for-loop is deprecated, so I am switching to the stride function.

In the first function, I use a c-style for-loop. In the second function, I used the stride function. createNumbers output is [8, 7, 6, 5, 4, 3, 2] createNumbers2 output is [8, 7, 6, 5, 4] I am trying to get the first output.

That means the stride function will ignore if I change the endIdx value. Is there a way to work around this, using stride, such that createNumbers2 also prints 3 and 2?

func createNumbers(lastNum: Int) -> [Int] {
    var endIdx = lastNum
    var answer : [Int] = []
    for var i = 8; i >= endIdx; i -= 1 {
        answer.append(i)
        if i == 6 {
            endIdx /= 2
        }
    }
    return answer
}

func createNumbers2(lastNum: Int) -> [Int] {
    var endIdx = lastNum
    var answer : [Int] = []
    for i in 8.stride(through: endIdx, by: -1) {
        answer.append(i)
        if i == 6 {
            endIdx /= 2
        }
    }
    return answer
}

print(createNumbers(4)) // [8, 7, 6, 5, 4, 3, 2]
print(createNumbers2(4)) // [8, 7, 6, 5, 4]

Solution

  • The stride method...

    Returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression less than or equal to end.

    So, as you noted, when you invoke the stride here

    for i in 8.stride(through: endIdx, by: -1) {
    

    the sequence is generated. Changing endIdx after this line will NOT change the sequence the for in will use.

    Solution

    Let's look at the logic you want to execute. Maybe we can find a better implementation.

    Please correct if I am wrong.

    You want to input an integer lastNum

    IF lastNum > 6 THEN OUTPUT 8,7

    ELSE OUTPUT 8,7,... [lastNum/2]

    Right?

    Then you can write it this way

    func numbers(lastNum: Int) -> [Int] {
        let biggest = 8
        guard lastNum <= biggest else { return [] }
        let smallest = lastNum > 6 ? lastNum : lastNum / 2
        return [Int](smallest...biggest).reverse()
    }
    

    Test

    numbers(4) // [8, 7, 6, 5, 4, 3, 2]
    

    More tests

    createNumbers(-1) == numbers(-1) // true
    createNumbers(0) == numbers(0) // true
    createNumbers(1) == numbers(1) // true
    createNumbers(2) == numbers(2) // true
    createNumbers(3) == numbers(3) // true
    createNumbers(4) == numbers(4) // true
    createNumbers(5) == numbers(5) // true
    createNumbers(6) == numbers(6) // true
    createNumbers(7) == numbers(7) // true
    createNumbers(8) == numbers(8) // true
    createNumbers(9) == numbers(9) // true
    

    Personal considerations

    This is just my point of view. I know the for in construct is still perfectly available in Swift. However building a value (an array in this case) using a for in does force us to use mutable values and imperative code. We should avoid both as much as possible because immutable values are safer and declarative code is more clear and potentially faster.