51

So, I have a for-loop that looks similar to this:

for var i = 0; i < results.count ; i += 1 {
   if (results[i] < 5) {
      results.removeAtIndex(i)
      i -= 1
   }
}

This used to work. But when I changed it to the preferred Swift 3.0 syntax:

for var i in 0..<results.count {
   if (results[i] < 5) {
      results.removeAtIndex(i)
      i -= 1
   }
}

I get an array IOOBE exception because it doesn't re-check the count and continues on until the original results.count.

How do I fix this? It works now, but I don't want to get into trouble in the future.

Unheilig
  • 16,196
  • 193
  • 68
  • 98
JoeVictor
  • 1,806
  • 1
  • 17
  • 38

4 Answers4

84

While the solution making use of filter is a fine solution and it's more Swift-ly, there is another way, if making use of for-in is, nonetheless, still desired:

func removeBelow(value: Int) {
    var results = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    for i in (0 ..< results.count).reversed() {
        if (results[i] < value) {
            results.remove(at: i)
        }
    }

    print(results)
}

removeBelow(value: 5)

Result:

[5, 6, 7, 8, 9, 10]

The problem with removeAtIndex within the loop is that it will not cause the array to re-index itself in-place and thus causing an array out of bounds exception due to count not being updated.

By traversing backwards, the out of bounds exception can thus be avoided.

DeyaEldeen
  • 10,847
  • 10
  • 42
  • 75
Unheilig
  • 16,196
  • 193
  • 68
  • 98
  • 2
    Oh god... that's really smart. Yeah I know that the filter way would be more *swiftly* but I think this way applies better, gonna use it. thanks! – JoeVictor Jun 04 '16 at 07:51
  • hooray, an editor of an OP that actually provides a well received answer, not just editing to increase rep. rare. – aremvee Mar 29 '17 at 11:39
  • @Unheiling - .reverse() this function not supported in swift3. Anybody can more help on same question? – New-Learner Jan 31 '18 at 11:28
  • You should remove the `var` keyword in `for var i in (0.. – atulkhatri Mar 13 '18 at 09:53
19

Could you use a filter instead?

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let greaterThan4 = numbers.filter{$0 >= 5}
print(greaterThan4)
DeyaEldeen
  • 10,847
  • 10
  • 42
  • 75
Brandon Shega
  • 769
  • 4
  • 17
  • 1
    The new array should be named `greaterThan4` ;) Anyway this is the correct way to solve the problem. – Luca Angeletti Jun 02 '16 at 16:36
  • Oh yeah right! the function that calls this is called "filterArray". I never made the connection haha. But I also need the filtered results to be appended to the end. Any suggestions? – JoeVictor Jun 02 '16 at 23:07
15

If you want to continue using a for-loop, you can enumerate over both index and element using enumerate:

for (index, element) in results.enumerate() {
   if (element < 5) {
     results.removeAtIndex(index)
   }
}

Although depending on what you're doing in your loop, the filter method might be a better idea.

Luka Jacobowitz
  • 22,795
  • 5
  • 39
  • 57
  • 8
    That's a bad idea, you will run into the out-of range trap under certain circumstances, for example try the array `[6, 5, 4, 3, 2, 1]`. The reason is that the array is modified in the loop but the index is not adjusted. – vadian Jun 02 '16 at 15:40
1

If your loop goes forward...

for var i in (0..<results.count) where results.indices.contains(i) { 

//if the index doesn't exist, the loop will be stopped.

if (results[i] < 5) {
        results.removeAtIndex(i) 
    }

}