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

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.

Related

Extremely basic loop question telling whether number is even/odd

var currentnum: Int = 1
for currentnum in 1...100{
if (currentnum % 2) != 0 {
print("Odd number")
}
else{
print("Even number")
}
currentnum += 1
}
Hello. I'm trying to "create a loop that iterates from 1 to 100 that prints out whether the current number in the iteration is even or odd." When I run the above code, I receive "error: expected expression after operator." What is wrong with my code (I'm new to programming). Thanks!
You don't need to declare var currentnum: Int = 1 in your code and increment through currentnum += 1. for-in loop does it for you. In Swift for-in syntax can be used to loop over both ranges of numbers, collections and even strings. All with the same syntax!
It should be as follows,
for currentnum in 1...100{
if (currentnum % 2) != 0 {
print("Odd number")
}
else{
print("Even number")
}
}
Good luck!
You should get rid of this expression currentnum += 1.
Because you are using the In .. Range operator there is no need to increment the counter. The In .. Range Operator will take care of this. This is different to the basic for loop from Java or C++ where you need to increment your counter variable.
Additionally the first declared variable currentnum is never used. This variable could be removed too.
The rest of your algorithm looks good and should work!
Hope this helps!
Unlike some languages you do not need to define the index variable before entering the for-in loop, nor do you need to manually increment the index.
You can also use the new swift function isMultiple(of:) rather than modulus
If you want to loop through a set range you can do:
for num in 1...100 {
if num.isMultiple(of: 2) {
print("\(num) is an even number")
} else {
print("\(num) is an odd number")
}
}
If you have a set of values in an array you can do this:
let numbers = [1,5,12,23,25,27,30,32,35]
for num in numbers {
if num.isMultiple(of: 2) {
print("\(num) is an even number")
} else {
print("\(num) is an odd number")
}
}
One liner without a for loop per the request in the comments
Array(1...100).map{$0 % 2 == 0 ? print("\($0) is even") : print("\($0) is odd") }
#BeginnerCoderGirl i have changed your code please check just remove currentnum += 1 from code and run

Swift: enumerated starting at 1?

In Swift, is it possible to enumerate a sequence starting at 1?
In my case, I'm using the SQLite C interface to bind values to prepared statements. The second argument of the sqlite3_bind_*() routines is the index of the SQL parameter to be set. The indices start at 1. (Ie, they're one-based.)
I could use Sequence.enumerated() and just add 1 to n inside each iteration, like so:
for (n, value) in values.enumerated() {
sqlite3_bind_int(stmt, Int32(n)+1, value)
}
But is there a way to start n from 1?
No, all collections indices in Swift are zero based but if you really want you can create your own custom enumeration zipping a range of Int32 values and the source collection:
extension Collection {
var enumerated: Zip2Sequence<PartialRangeFrom<Int32>, Self> { zip(1..., self) }
}
usage:
let values: [Int32] = [10, 20, 30]
for (n, value) in values.enumerated {
print("value:", value, "at:", n)
}
This will print
value: 10 at: 1
value: 20 at: 2
value: 30 at: 3
As others have said, array indexes start at 0 in Swift, so if you want to have 1-based indexes out of the box, you'll need to write some extra code.
If you're only using the index once then any workarounds might not worth the effort, and the incrementing at the call site is the most straightforward solution.
If however you will need to use the incremented index multiple times within the loop, another approach you could take would be to shadow the index:
for (n, element) in [1, 2,3].enumerated() {
let n = n + 1
sqlite3_bind_int(stmt, Int32(n), value)
}
Another approach could be using map():
for (n, element) in [1, 2,3].enumerated().map({($0+1,$1)}) {
sqlite3_bind_int(stmt, Int32(n), value)
}
, however not sure if you gain much with this solution, as the code is a little bit obscure.

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.

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]

C-Style i++ for is deprecated in swift issue

I want to increment index at some point for this loop it prints 1,3,5 which is i want to. I get the warning
C-Style for statement is deprecated and ...
i know what it means.
for var index=0; index<5; index++ {
//If condition A == true
index++
//else without index++
print(index) // print 1, 3, 5
}
So i changed it to:
for var index in 0..<5 {
//If condition A == true
index += 1
//else without index++
print(index) // print 1,2,3,4,5 Should 1,3,5 from my side
}
I just wondering why index not mutable? Even though i have set it to var or any solutions for my issue.
The index is not mutable because
for var x in y {
...
}
is equivalent to
for temp in y {
var x = temp
...
}
where the var just makes x a copy of temp. When you modify x, it won't modify the real index temp (This is also a reason why SE-0003 is introduced)
The C-style for loop can just be reduced to a while loop:
var index = 0
while index < 5 {
if conditionA {
index += 1
}
print(index)
index += 1
}
If you just need to enumerate odd numbers, the simplest way will be to use stride:
for i in 1.stride(through: 5, by: 2) {
print(i) // prints 1, 3, 5
}
Here is one way to get the results you want:
for var index in 0..<3 {
print(2 * index + 1)
}
I believe swift loop variables are immutable when using the for in loop. Mutating a loop variable inside the loop is usually (one could argue always) a bad idea so it makes sense the swift designers didn't allow it. The desired result can be accomplished in cleaner ways, for instance using continue.
for i in 1...5
{
if i%2==0
{
continue
}
print(i)
}