Iterating backwards with for-loop doesn't work - swift

I'm a newbie on Swift and I'm really confused about how to iterate backward with for-loop.
my array:
let arr = ["a", "b", "c"]
Trying for-loop that i googled:
for i in stride(from: arr.count, through: 0, by: -1) {
print(arr[i]) }
Output: Terminated by signal 4
Another attempt that doesn't work:
for i in arr.count...0 {
print(arr[i])
}
what am i doing wrong?

Both of them doesn't work because you start at arr.count, which is always an invalid index for an array. The last valid index is arr.count - 1, so changing the start of the stride/range to that will fix the problem.
If you want to iterate through the indices in reverse, you can just get the indices and reverse it:
for i in arr.indices.reversed() {
let element = arr[i]
}
Alternatively, you can use enumerated().reversed(), but note that reversed() here will first create an extra array to hold the reversed indices and elements, which means that you will be looping through arr an extra time.
for (i, element) in arr.enumerated().reversed() {
}

You missed the point that array indices – in almost all programming languages – are zero based, so the last index is count - 1
for i in stride(from: arr.count - 1, through: 0, by: -1) {
print(arr[i])
}

Related

Swift 5 Thread 1: Fatal error: Index out of range

I'm looping through an array using the array count. The code will run once and then after that I get an index out of range error. My code is below. I can't figure out why I'm getting this error. Can someone please let me know what I'm missing?
for stockItem in stride(from: 0, through: self.posts.count, by: 1) {
guard let url = URL(string: "https://api.tdameritrade.com/v1/marketdata/\(self.posts[stockItem].symbol)/quotes") else {
print("URL does not work")
fatalError("URL does not work!")
}}
The problem with stride(from:through:by:) is that it includes that final value supplied to through. Consider:
let strings = ["foo", "bar", "baz"]
for index in stride(from: 0, through: strings.count, by: 1) {
print(index)
}
That will print four values (!):
0
1
2
3
If you tried to use that index as a subscript in the array ...
for index in stride(from: 0, through: strings.count, by: 1) {
print(index, strings[index])
}
... it would work for the first three indexes, but that fourth one would fail because there are only three items in the array:
0 foo
1 bar
2 baz
Fatal error: Index out of range
You could solve this by using to, instead, striding up to, but not including, that final value:
for index in stride(from: 0, to: strings.count, by: 1) {
print(index, strings[index])
}
That would stop at the third entry, and everything would be good:
0 foo
1 bar
2 baz
All of that having been said, we wouldn’t generally use stride at all with a by value of 1. We’d just use a half-open range operator, ..<:
for index in 0 ..< strings.count {
print(strings[index])
}
Or, better, you might instead use:
for index in strings.startIndex ..< strings.endIndex {
print(strings[index])
}
Or, better, use indices:
for index in strings.indices {
print(strings[index])
}
The use of indices becomes essential if you happen to be working with slices of arrays, where one cannot assume the appropriate values, or if you happen to be dealing with some random access collection that does not happen to use numeric indices.
Or, since you don’t really need that index, you would just do:
for string in strings {
print(string)
}
Or, in your case:
for post in posts {
let url = URL(string: "https://api.tdameritrade.com/v1/marketdata/\(post.symbol)/quotes")!
...
}
You used through instead of to.
But there’s no reason to use a stride! Iterate more meaningfully and you’ll avoid this problem better.

Loop through a range of an Array [duplicate]

This question already has answers here:
How to loop through an array from the second element in elegant way using Swift
(5 answers)
Closed 3 years ago.
How can I loop through an array range? Example if I had 5 objects in an array. I want to loop from index [3] to end of the array in this example it would go through and update objects 3-5 and skip objects 1 & 2. This is what I have so far using the stride method(this code isn't working). Is this the correct method? How can I achieve this?
stride(from: markers[index], to: markers.endIndex, by: 1).forEach { i in
// Do something for each array object
}
You can use the range operator to get sequences of indices or slices of the array. Which you use depends on what you are trying to do. For clarity I am going to leave out error checking.
For example:
let letters = ["a", "b", "c", "d", "e"]
letters[3...].forEach { print($0) } // prints d e
// or you can print up to index 3
letters[...3].forEach { print($0) } // prints a b c d
// or print elements 1-3 inclusive
letters[1...3].forEach { print($0) } // prints b c d
// or print elements 1-3 excluding index 3
letters[1..<3].forEach { print($0) } // prints b c d
If you wanted to modify the elements of the array you pass in the indices rather than the elements
var mutableLetters = ["a","b","c","d","e"]
(3..<mutableLetters.count).forEach {
mutableLetters[$0] = mutableLetters[$0].uppercased()
}
notice here we need to specify both limits because the range knows nothing about the array.
It's often more Swifty not to modify things in place so, if this fits your use case you might consider something like this:
let immutableLetters = ["a","b","c","d","e"]
let upperCasedFromThreeOn = immutableLetters[3...].map { $0.uppercased() }
// upperCasedFromThreeOn = ["D","E"]
As a final note, sometimes you need to know both the index and the element. You can use a forEach on the indices as above, but another way is to use enumerated() this creates a tuple of the index and element.
let range = 2...4
immutableLetters.enumerated()
.filter { (index,_) in range.contains(index) }
.forEach { (index, element) in
print("\(index) \(element)")
}
Here I've used a filter after the enumeration so that the indices match the original array.
You can simply iterate over your array slice dropping the first n elements:
let markers = ["a","b","c","d","e"]
for marker in markers.dropFirst(2) {
print(marker) // this will print 'c d e'
}
If you need to change your array you can iterate over a slice of its indices:
let markers = ["a","b","c","d","e"]
for index in markers.indices.dropFirst(2) {
print(markers[index])
}
You can simply loop through the array using Range Operator in Swift like,
var markers = ["M1","M2","M3","M4","M5"]
let count = markers.count
if count > 2 {
for i in 2..<count {
//add your code here..
}
}
In the above code, I've used half-open range operator(..<)

Swift Forin-where perform filter or skip (continue)?

I just gain a knowledge about Swift's forin-where, but it seems very lack of documents. So there's a question in my mind: Does it perform filter then loop or just loop with condition? given the below code
var arr = [1, 2, 3, 4, 5]
for i in arr where i > 3 {
print(i)
}
does the machine do like this:
for i in arr.filter { $0 > 3 }
or like this?
for i in arr {
guard i > 3 else { continue }
print(i)
}
Should I use forin-where or just filter then foreach?
It is the latter (iterate over all elements, execute the body only for
elements satisfying the condition).
The for-statements takes an arbitrary sequence, not only arrays.
Filtering the sequence first would not only be inefficient (memory- and
time-wise), but also impossible for sequences producing “infinitely many” values, like in this example:
for x in 1... where x % 3 == 0 {
print(x)
if x > 10 { break }
}
Here 1... is a “partial range” representing all integers greater than
or equal to one.

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.
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).
Use stride:
for i in stride(from: allowedItems.count - 1, through: 0, by: -1) {
}
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]

Index out of range in Swift with removeAtIndex

I tried to remove an element in NSUserDefaults which is the same as stockSymbol's value when click a button. My idea is that cast the NSUserDefaults to an array and remove the element with removeAtIndex. Here is my code.
#IBAction func buttonFilledStarClicked(sender: AnyObject) {
NSLog("Filled star clicked")
self.buttonFilledStar.hidden = true
self.buttonEmptyStar.hidden = false
var Array = NSUserDefaults.standardUserDefaults().objectForKey("favorites")! as! [String]
var countArray = (NSUserDefaults.standardUserDefaults().objectForKey("favorites")! as! [String]).count - 1
for i in 0...countArray {
if stockSymbol! == Array[i] {
NSLog("i is : \(i)")
Array.removeAtIndex(i)
}
else {}
}
NSLog("Array is: \(Array), countArray is: \(countArray)")
}
However it has 'out of index' error.
It works when I just comment Array.removeAtIndex(i) out.
Array looks like this --
["aa", "bb", "Test!", "Test!"]
Any suggestions? Thank you in advance.
So, the change you can make to resolve the error with the least impact on your code overall would be to simply iterate through the indices backwards:
for i in (0...countArray).reverse() {
if stockSymbol! == Array[i] {
NSLog("i is : \(i)")
Array.removeAtIndex(i)
}
else {}
}
But the best option is to just use Swift's filter:
Array = Array.filter { $0 != stockSymbol }
An expanded note on why the crash is happening...
Let's take a simplified example. Say I have the following array:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
And I want to remove all of the odd numbers out of it. Using your first naïve approach, I might write my logic like this:
for i in 0..<arr.count {
if arr[i] % 2 != 0 {
arr.removeAtIndex(i)
}
}
Look at what happens on each iteration.
On the first iteration, we have arr[i] of 1. This is an odd number, so we'll removeAtIndex, and our array now actually looks like this:
[2, 3, 4, 5, 6, 7, 8, 9, 10]
The array's size is now smaller--it has just 9 elements. But the loop doesn't work like an old C-style for loop where i < arr.count is checked on each iteration (which is part of why this loop is faster).
But notice something else that happens when we iterate forward...
On the second iteration, i is equal to 1, and so what does arr[i] give us? It gives us 3. We never even check 2. On the first iteration, when i was 0, it was at index 1. On the second iteration, when i is 1, the 2 is at index 0.
So on the second iteration, we'll call removeAtIndex with i equal to 1 and remove the 3.
This pattern will continue for a few iterations until we end up with our array of just even numbers:
[2, 4, 6, 8, 10]
But this happens after the iteration where i was equal to 4, and the loop is going to try running until i is equal to 10.
On the sixth iteration of the loop, we try to access the element at index 5 of the array. But the array only has five elements, so the largest index is 4. When we try to access index 5, we crash.
You should not remove it from inside of the loop, when you call removeAtIndex(i) Array removes the item so you have 1 less item then countArray.. so you have to have another array to remember which item you want to remove and remove it outside of the loop.. or better option is to use filter
// Filter only strings that match stockSymbol
Array = Array.filter { $0 == stockSymbol! }
Try to find out which i that give you index out of range result. Is it the 0 or the last one. Maybe you will find some other useful clue.
First of all Array is a type, maybe you can call:
var favourites = NSUserDefaults.standardUserDefaults().objectForKey("favorites")! as! [String]
Then you don't need a array's count variable you can access using count arrays property.
Finally if you are iterating through an array and remove an element it always going to throw "index's error" because the index is not the same as the beginning..
For solving this you can take two pointers of the index variable, but what I would do is something like this:
var correctElements = favourites.filter({$0!=stockSymbol})