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

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.

Related

change the array by task

How to put the same, but positive, before each negative element of the array. Count the number of inserted elements
example:
var arrayInt: [Int] = [22, 16, -39, 1, -200]
result: [22, 16, 39, -39, 1, 200, -200]
what to use for-in or method .map or method .filter
Thanks for the help!
Here's a relatively simple implementation:
let result: [Int] = arrayInt.reduce(into: []) { acc, item in
if item < 0 {
acc.append(-item)
}
acc.append(item)
}
reduce allows you to transform an array into an arbitrary type. In this case, you still want an [Int], but you don't want to be constrained to the same number of elements (like map would do).
If you need the number of inserted elements:
let inserted = result.count - arrayInt.count
Note that you could also build this into the result by returning a tuple with a count instead of just an [Int]
Keep in mind this is not the only possible solution -- just a relatively straightforward one.
In response to the comments, you could also turn this into a one-liner with something like flatMap: arrayInt.flatMap { $0 < 0 ? [-$0, $0] : [$0] }

Iterating backwards with for-loop doesn't work

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])
}

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 lazy subscript ignores filter

How does subscripting a lazy filter work?
let ary = [0,1,2,3]
let empty = ary.lazy.filter { $0 > 4 }.map { $0 + 1 }
print(Array(empty)) // []
print(empty[2]) // 3
It looks like it just ignores the filter and does the map anyway. Is this documented somewhere? What other lazy collections have exceptional behavior like this?
It comes down to subscripting a LazyFilterCollection with an integer which in this case ignores the predicate and forwards the subscript operation to the base.
For example, if we're looking for the strictly positive integers in an array :
let array = [-10, 10, 20, 30]
let lazyFilter = array.lazy.filter { $0 > 0 }
print(lazyFilter[3]) // 30
Or, if we're looking for the lowercase characters in a string :
let str = "Hello"
let lazyFilter = str.lazy.filter { $0 > "Z" }
print(lazyFilter[str.startIndex]) //H
In both cases, the subscript is forwarded to the base collection.
The proper way of subscripting a LazyFilterCollection is using a LazyFilterCollection<Base>.Index as described in the documentation :
let start = lazyFilter.startIndex
let index = lazyFilter.index(start, offsetBy: 1)
print(lazyFilter[index])
Which yields 20 for the array example, or l for the string example.
In your case, trying to access the index 3:
let start = empty.startIndex
let index = empty.index(start, offsetBy: 3)
print(empty)
would raise the expected runtime error :
Fatal error: Index out of range
To add to Carpsen90's answer, you run into one of Collection's particularities: it's not recommended, nor safe to access collections by an absolute index, even if the type system allows this. Because the collection you receive might be a subset of another one.
Let's take a simpler example, array slicing:
let array = [0, 1, 2, 3, 4]
let slice = array[2..<3]
print(slice) // [2]
print(slice.first) // Optional(2)
print(slice[0]) // crashes with array index out of bounds
Even if slice is a collection indexable by an integer, it's still unsafe to use absolute integers to access elements of that collection, as the collection might have a different set of indices.

In Swift 2.2, how to consolidate the for-loop statement?

In Swift 2.2, C-style for statement is deprecated, so I modify following for-loop:
for var idx=data.count-1; idx>=0; --idx
into
for idx in (0...data.count-1).reverse() // <--- new statement
However, I found, when data.count is 0 during execution, the new statement will crash with error fatal error: Can't form range with end < start.
Is there a best/standard way to code for this case?
P.S. I think I have to use different kinds of loops/syntax to replace my unified C-style loops. Any further comment or suggestion on this is welcome.
Creating a range as
0 ... data.count-1
terminates with a runtime exception if data.count is zero. It is
often better to use the ..< operator to make a range that
omits its upper value, in your case:
0 ..< data.count
This works for data.count == 0 as well and creates an empty
range in that case. This applies to
both forward and backward iteration:
for idx in 0 ..< data.count { ... }
for idx in (0 ..< data.count).reverse() { ... }
(Of course stride() is a sensible alternative for the second case.)
You should use Strideable.stride(through:by:) to generate your for-loop range, like this:
for idx in (data.count-1).stride(through: 0, by: -1) {
print(idx)
}
It works even if data.count == 0.
In such a simple loop, there is no need for arithmetic operations.
If data is an array, use indices:
for index in data.indices {
}
for index in data.indices.reverse() {
}
or access the data directly
for item in data {
}
for item in data.reverse() {
}
or a combination of the previous using enumerate
for (index, item) in data.enumerate() {
}
Note that all for-in loops above can be also written as forEach:
data.indices.forEach {
}
Instead, use for idx in (0..<data.count).reverse(). This will form the empty range that you want when data.count == 0.