Casey Perkins
Casey Perkins

Reputation: 1932

safely remove item while iterating backward in Swift 3

When I want to pass through and remove an item or items from an array (when certain conditions are met), I typically iterate backward in the C-style for-loop and remove the item by index, avoiding the problem of index numbers being changed of the next item to be processed, or the changing size of the list affecting how many times the loop is passed through. But the C for-loop has been removed in Swift 3.

Here is my Swift 2.3 code for the initialization of the loop:

for (var i = allowedItems.count - 1;  i > -1;  i -= 1)

Here is the monstrosity created by the Swift 3 converter:

for (i in ((-1 + 1)...allowedItems.count - 1).reversed())

This version does not compile however. ("Expected ',' separator" at the "in" operator).

I simplify the "-1 + 1" bit to zero:

for (i in (0...allowedItems.count - 1).reversed())

Now the error is "Expected Sequence expression for for-each loop".

What is the safe and hopefully reasonably elegant way of iterating backward in Swift 3, in which an index or counter variable is made available for use in specifying which item should be removed? This type of logic appears a number of places in my code so I want to make sure to find the best solution.

Thanks.

Upvotes: 0

Views: 971

Answers (3)

dfrib
dfrib

Reputation: 73206

What is the safe and hopefully reasonably elegant way of iterating backward in Swift 3, in which an index or counter variable is made available for use in specifying which item should be removed?

This doesn't answer the technical question, but possibly the underlying XY problem: have you considered simply filtering your array based on the criteria "when certain conditions are met"?

func certainConditionsForKeepingAreMet(_ element: YourElementType) -> Bool { /* ... */ }
allowedItems = allowedItems.filter(certainConditionsForKeepingAreMet)

E.g.

var allowedItems = [1, 3 ,6, 2]
func certainConditionsForKeepingAreMet(_ element: Int) -> Bool { return element < 3 }
allowedItems = allowedItems.filter(certainConditionsForKeepingAreMet)
print(allowedItems) // [1, 2]

If you'd like to remove and use the removed elements (on-the-fly), you could simply pipe the elements that are to be removed to some "use this element" function, in the course of checking the conditions for the elements.

func doSomethingWith(_ element: Int) { print("Removed", element) }

func certainConditionsForKeepingAreMet(_ element: Int) -> Bool {
    if element >= 3 {
        doSomethingWith(element)
        return false
    }
    return true
}

var allowedItems = [1, 3 ,6, 2]
allowedItems = allowedItems.filter(certainConditionsForKeepingAreMet)
/* Removed 3
   Removed 6 */
print(allowedItems) // [1, 2]

Upvotes: 2

matt
matt

Reputation: 535801

What is the safe and hopefully reasonably elegant way of iterating backward in Swift 3

The built-in way is:

for i in (0 ..< allowedItems.count).reversed()

The elegant way is:

for i in allowedItems.count >>> 0

(where >>> is the custom operator that I define here).

Upvotes: 3

Code Different
Code Different

Reputation: 93181

Use stride:

for i in stride(from: allowedItems.count - 1, through: 0, by: -1) {

}

Upvotes: 2

Related Questions