Juanjo Conti
Juanjo Conti

Reputation: 30043

Swift: how to perform a task n-1 times where n is the length of an array?

I came to this:

for x in 1...(myArray.count - 1) { task() }

which is very ugly. Is there a better way?

Upvotes: 3

Views: 106

Answers (3)

John Goering
John Goering

Reputation: 39040

Here's a way with Swift 2:

for x in myArray where x != myArray.first { task() }

Upvotes: 0

Airspeed Velocity
Airspeed Velocity

Reputation: 40963

You have to be a little careful, as if the array is empty, this will crash:

let a: [Int] = []
let range = 0..<a.count-1
// fatal error: Can't form Range with end < start

Strides don’t have this problem (since Strideable things must be Comparable) so you could do:

for _ in stride(from: 0, to: a.count - 1, by: 1) {
    // will only execute if a.count > 2
    print("blah")
}

Alternatively, if you guard it, you can use dropFirst:

for _ in (a.isEmpty ? [] : dropFirst(a))  {
    print("blah")
}

I would strongly advise against trying to make this look neater by creating a pseudo-for-loop that runs one less than the count times. There’s a reason that there’s no forEach or repeat functions in Swift. These kind of loops seem nice at first but there are lots of bugs that arise from them (for example, return or continue don’t work the way you might expect, also it’s generally considered bad practice to use a higher-order function to do external mutation – whereas a regular for loop is a suggestion that mutation/side-effects are likely).

The neatest extension-type solution would probably be to extend Array to do a safe drop-first:

extension Array {
    // version of dropFirst that returns empty array for both
    // empty and single-element array
    func safeDropFirst() -> ArraySlice<T> {
        return self.isEmpty ? [] : dropFirst(self)
    }
}

for _ in myArray.safeDropFirst() {
    doThing()
}

Upvotes: 5

Jean-Philippe Pellet
Jean-Philippe Pellet

Reputation: 60006

Not much better, but:

for _ in 1..<myArray.count { task() }

But this will crash if myArray is empty (thanks, Airspeed Velocity).

If you happen to need that a lot for some reason, you can provide your own “loop abstraction” and take care of that issue too:

func repeatArray<T>(arr: [T], @noescape f: () -> Void) {
    if !arr.isEmpty {
        for _ in 1..<arr.count {
            f()
        }
    }
}

repeatArray(myArray) {
    task()
}

Upvotes: 4

Related Questions