1

Consider the following code:

let value = "ABCDE"

for letter in value {
    print(letter)
}

This prints:

A
B
C
D
E

Now consider the hypothetical code that when it finds a '2', it skips 2 forward in the loop. Here's pseudo-code showing what I'm asking...

let value = "AB2DE"

for letter in value {

    if letter == "2" {
        continue 2 <-- Does Swift support something similar to this?
    }

    print(letter)
}

The above would make the output look like this:

A
B
E

Note: Of course I can easily achieve this via managing a skip-counter, then using continue, decrementing the counter as I go, or, if the source supports subscripting, as others have suggested I can use a while loop where I manage my own index. There are a myriad ways to solve this hypothetical problem, but that's not the intent of my question.

My question is specifically to find out if the language natively supports skipping more than one iteration of a for-loop at a time (e.g. something like continueMany or continue x).

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • 1
    This might help you: https://stackoverflow.com/questions/37170203/swift-3-for-loop-with-increment/37250498 – Cristik Mar 10 '18 at 17:06
  • That's using a fixed interval. I need something conditional. I think a 'while' loop with my own index is the way to go. – Mark A. Donohoe Mar 10 '18 at 17:12
  • Are you expecting for the language to decode strings into ints for you? :) – Cristik Mar 10 '18 at 17:14
  • Also, should the newly decoded int be kept for the remaining characters? Or it should be used only once? – Cristik Mar 10 '18 at 17:16
  • Also, should we consider only single-digit numbers, or any valid number? – Cristik Mar 10 '18 at 17:17
  • To me, it seems the question is incomplete – Cristik Mar 10 '18 at 17:17
  • I've updated the question. Please check out the last sentence, in bold. :) – Mark A. Donohoe Mar 10 '18 at 17:22
  • That makes it clearer, but I think that now you no longer need most of the rest of the question, since it distracts form the actual goal (kinda like a red herring :) – Cristik Mar 10 '18 at 17:24
  • The difficulty with presenting a made-up, nonsensical scenario is that there often is a good way to solve your _actual_ problem that has [nothing to do with the solution that you're attempting to resolve](https://meta.stackexchange.com/a/66378/159251). – jscs Mar 10 '18 at 17:30
  • There is no 'actual problem'. That is my entire point. I'm asking about a language feature (if one exists) and I created an example to illustrate what I was asking. – Mark A. Donohoe Mar 10 '18 at 17:31
  • @MarqueIV: Compare https://stackoverflow.com/a/39903837/1187415 for a general-purpose for-loop replacement based on `sequence`. – Martin R Mar 10 '18 at 17:46
  • Thanks @MartinR! Quick question though, it looks like all such iterations are based on numbers, not just a sequence of 'Foo' objects as it's doing integer math to determine if it's a valid value. How can that be used if you have, say, an array of `Foo` objects? – Mark A. Donohoe Mar 10 '18 at 17:48
  • By the time you're asking about a specific technique, you are in the "present a solution" phase. The problem you're trying to solve is a step or two back: there's some algorithm you're trying to instantiate, and the for loop is one possible way to do that. – jscs Mar 10 '18 at 17:48
  • 1
    @MarqueIV: You would iterate over the collections *indices,* and conditionally advance it by one or more. Essentially the same as a while-loop as rmaddy suggested below, only that it resembles traditional for-loops, that the iteration variable is a *constant* inside the body, and its scope is limited to the loop. – Martin R Mar 10 '18 at 17:57

4 Answers4

5

No, you can't change how a for-in loop iterates from within the loop.

A while loop with your own index counter is probably the simplest solution in this case. Though you may be able to make use of sequence(first:next:) in a for-in loop.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
2

You have to use a while loop. Due to the way string index advancement work in Swift, this is a little complicated compared to many other languages:

let value = "AB2DE"

var index: String.Index? = value.startIndex
while let charIndex = index, charIndex < value.endIndex {
    let letter = value[charIndex]

    if let digit = Int(String(letter)) {
        index = value.index(charIndex, offsetBy: digit, limitedBy: value.endIndex)
    } else {
        index = value.index(after: charIndex)
        print(letter)
    }
}
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Yeah, this is a way to solve the demonstrated problem, but I'm more asking about what the language natively supports. That's why I put the caveat up top. Starting to think the answer is 'Swift doesn't provide native support for skipping more than one iteration'. – Mark A. Donohoe Mar 10 '18 at 17:16
1

The answer to the last paragraph in the question is no - Swift doesn't have a continue N-like statement.

You can use some other language constructs, though, to achieve similar results. Using reduce to carry the number if elements to skip is one of them:

_ = value.reduce(0) { skip, char in
    // skip while we need to
    guard skip <= 0 else { return skip - 1 }
    // attempt to get a new skip count
    let skip = Int(String(char)) ?? 0
    if skip <= 0 {
        // do the actual work
        print(char)
    }
    // decrement the skip count
    return skip - 1
}

Or, a more imperative approach, would be to extend Iterator with a skip method, and use the iterator for walking through the collection (a for loop over a collection is just a sugar syntax over walking over an iterator) :

extension IteratorProtocol {
    @discardableResult
    mutating func skip(_ n: Int) -> Element? {
        guard n > 0 else { return next() }
        return skip(n-1)
    }
}

var it = value.makeIterator()
while let char = it.next() {
    if let n = Int(String(char)) {
        it.skip(n)
    } else {
        print(char)
    }
}
Cristik
  • 30,989
  • 25
  • 91
  • 127
0

You could construct your custom "skipping" sequence, using a provided Element -> Int? predicate to

  • if nil: don't skip next iteration, or
  • if .some(number): skip next number iterations

And thereafter use this construct to iterate over your sequence (value) and conditionally jump forward.

E.g.:

struct SkippingSequence<T: Sequence>: IteratorProtocol, Sequence {
    var iterator: T.Iterator
    let skipPredicate: (T.Element) -> Int?
    // skipPredicate:
    // - nil means no skip,
    // - .some(number) means to skip next 'number' of iterations.
    init(_ collection: T, _ skipPredicate: @escaping (T.Element) -> Int?) {
        self.iterator = collection.makeIterator()
        self.skipPredicate = skipPredicate
    }

    mutating func next() -> T.Element? {
        var n = iterator.next()
        if let skipNumIterations = n.flatMap({ skipPredicate($0) }) {
            for _ in 0..<skipNumIterations { n = iterator.next() }
        }
        return n
    }
}

Applied for you example:

let collection = "AB2DEF3HIJK"
for letter in SkippingSequence(collection, { Int(String($0)) }) {
    print(letter)
} /* A
     B
     E (skipped 2, D)
     F
     J (skipped 3, H, I)
     K */

The skipPredicate naming doesn't really explain its purpose, and could be renamed to something with better semantics.

dfrib
  • 70,367
  • 12
  • 127
  • 192