Swift 3 for loop with increment
How do I write the following in Swift3?
for (f = first; f <= last; f += interval)
{
n += 1
}
This is my own attempt
for _ in 0.stride(to: last, by: interval)
{
n += 1
}
Solution 1:
Swift 2.2 -> 3.0: Strideable
:s stride(...)
replaced by global stride(...)
functions
In Swift 2.2, we can (as you've tried in your own attempt) make use of the blueprinted (and default-implemented) functions stride(through:by:)
and stride(to:by:)
from the protocol Strideable
/* Swift 2.2: stride example usage */ let from = 0 let to = 10 let through = 10 let by = 1 for _ in from.stride(through, by: by) { } // from ... through (steps: 'by') for _ in from.stride(to, by: by) { } // from ..< to (steps: 'by')
Whereas in Swift 3.0, these two functions has been removed from Strideable
in favour of the global functions stride(from:through:by:)
and stride(from:to:by:)
; hence the equivalent Swift 3.0 version of the above follows as
/* Swift 3.0: stride example usage */ let from = 0 let to = 10 let through = 10 let by = 1 for _ in stride(from: from, through: through, by: by) { } for _ in stride(from: from, to: to, by: by) { }
In your example you want to use the closed interval stride alternative stride(from:through:by:)
, since the invariant in your for
loop uses comparison to less or equal to (<=
). I.e.
/* example values of your parameters 'first', 'last' and 'interval' */
let first = 0
let last = 10
let interval = 2
var n = 0
for f in stride(from: first, through: last, by: interval) {
print(f)
n += 1
} // 0 2 4 6 8 10
print(n) // 6
Where, naturally, we use your for
loop only as an example of the passage from for
loop to stride
, as you can naturally, for your specific example, just compute n
without the need of a loop (n=1+(last-first)/interval
).
Swift 3.0: An alternative to stride
for more complex iterate increment logic
With the implementation of evolution proposal SE-0094, Swift 3.0 introduced the global sequence
functions:
-
sequence(first:next:)
, -
sequence(state:next:)
,
which can be an appropriate alternative to stride
for cases with a more complex iterate increment relation (which is not the case in this example).
Declaration(s)
func sequence<T>(first: T, next: @escaping (T) -> T?) -> UnfoldSequence<T, (T?, Bool)> func sequence<T, State>(state: State, next: @escaping (inout State) -> T?) -> UnfoldSequence<T, State>
We'll briefly look at the first of these two functions. The next
arguments takes a closure that applies some logic to lazily construct next sequence element given the current one (starting with first
). The sequence is terminated when next
returns nil
, or infinite, if a next
never returns nil
.
Applied to the simple constant-stride example above, the sequence
method is a bit verbose and overkill w.r.t. the fit-for-this-purpose stride
solution:
let first = 0
let last = 10
let interval = 2
var n = 0
for f in sequence(first: first,
next: { $0 + interval <= last ? $0 + interval : nil }) {
print(f)
n += 1
} // 0 2 4 6 8 10
print(n) // 6
The sequence
functions become very useful for cases with non-constant stride, however, e.g. as in the example covered in the following Q&A:
- Express for loops in swift with dynamic range
Just take care to terminate the sequence with an eventual nil
return (if not: "infinite" element generation), or, when Swift 3.1 arrives, make use of its lazy generation in combination with the prefix(while:)
method for sequences, as described in evolution proposal SE-0045. The latter applied to the running example of this answer makes the sequence
approach less verbose, clearly including the termination criteria of the element generation.
/* for Swift 3.1 */
// ... as above
for f in sequence(first: first, next: { $0 + interval })
.prefix(while: { $0 <= last }) {
print(f)
n += 1
} // 0 2 4 6 8 10
print(n) // 6
Solution 2:
With Swift 5, you may choose one of the 5 following examples in order to solve your problem.
#1. Using stride(from:to:by:)
function
let first = 0
let last = 10
let interval = 2
let sequence = stride(from: first, to: last, by: interval)
for element in sequence {
print(element)
}
/*
prints:
0
2
4
6
8
*/
#2. Using sequence(first:next:)
function
let first = 0
let last = 10
let interval = 2
let unfoldSequence = sequence(first: first, next: {
$0 + interval < last ? $0 + interval : nil
})
for element in unfoldSequence {
print(element)
}
/*
prints:
0
2
4
6
8
*/
#3. Using AnySequence
init(_:)
initializer
let anySequence = AnySequence<Int>({ () -> AnyIterator<Int> in
let first = 0
let last = 10
let interval = 2
var value = first
return AnyIterator<Int> {
defer { value += interval }
return value < last ? value : nil
}
})
for element in anySequence {
print(element)
}
/*
prints:
0
2
4
6
8
*/
#4. Using CountableRange
filter(_:)
method
let first = 0
let last = 10
let interval = 2
let range = first ..< last
let lazyCollection = range.lazy.filter({ $0 % interval == 0 })
for element in lazyCollection {
print(element)
}
/*
prints:
0
2
4
6
8
*/
#5. Using CountableRange
flatMap(_:)
method
let first = 0
let last = 10
let interval = 2
let range = first ..< last
let lazyCollection = range.lazy.compactMap({ $0 % interval == 0 ? $0 : nil })
for element in lazyCollection {
print(element)
}
/*
prints:
0
2
4
6
8
*/
Solution 3:
Simply, working code for Swift 3.0:
let (first, last, interval) = (0, 100, 1)
var n = 0
for _ in stride(from: first, to: last, by: interval) {
n += 1
}