Elegant Swift way to handle negative index in for loop - swift

I'm new to Swift and trying to find an elegant way to handle a for loop variable that can be negative.
func funForLoops(_ loop:Int) {
for i in 0..<loop {
print("Hello \(i)")
}
}
funForLoops(1) // prints Hello 0
funForLoops(0) // doesn't execute
funForLoops(-1) // runtime error "fatal error: Can't form Range with upperBound < lowerBound"
Is there a simpler way to check this than this:
if (loop >= 0) {
for i in 0..<loop {
print("Hello \(i)")
}
}
Or this:
for i in 0..<(loop >= 0 ? loop : 0) {

On the assumption you mean "if it's negative, do nothing," (which is not obvious; you might mean "decrement," which is why it would be ambiguous if it weren't an exception) the syntax you want is:
for i in 0..<max(0, loop) { }
This is a fine syntax when it is necessary, but in most cases if the value can be surprisingly negative, you have a deeper problem in the structure of the program and should have resolved the issue sooner.

Yeah, its not obvious what result you want to have.
If you just need to iterate, no matter negative or not
func forLoop(count: Int) {
for i in min(count, 0)..<max(0, count) {
print(i)
}
}
Or less code
func forLoop(count: Int) {
for i in 0..<abs(count) {
print(i)
}
}
The only difference here is that first example will produce output with negative values and stops before 0.
Second example will start from 0 and finish with count-1

Related

Swift function compiler error 'missing return'

I've been trying to get this function to return a Bool value but I don't understand why i'm getting the error "missing return in a function expected to return 'Bool'. I've been looking around online and tried different things but I can't seem to find a solution. Any help would be appreciated!
func trueSquare(a:[Int], b:[Int]) -> Bool {
for i in b[0]...b.endIndex {
if b[i] == a[i]*a[i] {
return true
}
else {
return false
}
}
}
EDIT: I have changed the loop to for i in 0...(b.count - 1) but I am still getting the same error even when I call the function with a and b both having the same numbers of elements.
What if your array has no element? Then for each loop never runs and then your method returns nothing which is obviously wrong. So you need to return value even outside of the loop.
But, your logic is bad. You're returning boolean value depending on if just first element from b is equal to a*a.
So, logic should be something like: if every element meets the condition, then return true, otherwise, return false. To achieve this, in Swift 4.2+ you can use method allSatisfy
func trueSquare(a:[Int], b:[Int]) -> Bool {
guard a.count == b.count else { return false } // if arrays have different number of elements, return false
return a.enumerated().allSatisfy {$0.element * $0.element == b[$0.offset]}
}
I suspect the compiler requires a return value for the case when the loop is not executed at all.
Now, a ClosedRange can never be empty, so b[0]...b.endIndex won't ever be empty (if it results in an empty or invalid range, the code would crash), but the compiler is not smart enough to know that.
PS: Are you sure b[0]...b.endIndex is actually the sequence you want to loop over. This creates a range from the first element of b to the endIndex of b. That doesn't make any sense to me.
Your function does not handle case where b is an empty array.
You need to define what you want the return value to be for such case, because your loop will be skipped when b is an empty array.
Secondly, your logic is also incomplete, because if the condition is good for i==0, you immediately return true, without checking the rest of the items.
Thirdly, you probably want to make sure a and b have same length.
So here is what your function should look like:
func trueSquare(a:[Int], b:[Int]) -> Bool {
if a.count != b.count {
return false
}
for i in 0..<b.count {
if b[i] != a[i]*a[i] {
return false
}
}
return true
}

Argument labels do not match any availble overloads

I'm trying to create an anagram tester, and I'm pretty sure the code I have should work, but I'm getting an error 'Argument labels '(_:)' do not match any available overloads' I've looked at the other posts regarding the same error, but I'm still not sure what this means or how to fix it.
var anagram1 : String!
var anagram2 : String!
var failure : Bool = false
var counter : Int = 0
print("Please enter first word: ")
anagram1 = readLine()
print("Please enter Second word: ")
anagram2 = readLine()
if anagram1.count == anagram2.count {
for i in anagram1.characters{
if (!failure){
failure = true
for y in anagram2.characters {
counter += 1
if i == y {
failure = false
anagram2.remove(at: String.Index(counter)) // error here
}
}
}
else {
print("these words are not anagrams")
break;
}
}
if (!failure) {
print("these words ARE anagrams")
}
}
else{
print ("these words aren't even the same length you fucking nonce")
}
To answer your first question: the error message Argument labels '(_:)' do not match any available overloads means that you've given a function parameter names or types that don't match anything Swift knows about.
The compiler is also trying to tell you what parameters to look at. '(_:)' says that you're calling a function with an unlabeled parameter. (That means a value without any parameter name. A common example of a function that would look like this is print("something"). In Swift documentation, this would look like print(_:).
Finally, overloads are ways to call a function with different information. Again using the print function as an example, you can call it multiple ways. A couple of the most common overloads would be:
// print something, followed by a newline character
print("something")
// print something, but stay on the same line
// (end with an empty string instead of the default newline character)
print("something", terminator: "")
Documented, these might look like print(_:) and print(_:, terminator:).
Note: these are broken down for explanation. The actual Swift documentation shows func print(_: Any..., separator: String, terminator: String) which covers a number of different overloads!
Looking at the line where the error occurs, you see a function call and an initializer (which is essentially a function). Documented, the way you've entered the parameters, the functions would look like: remove(at:) and String.Index(_:).
String.Index(_:) matches the parameters of the error message, so that's where your error is. There is no overload of the String.Index initializer that takes an unnamed parameter.
To fix this error, you need to find the correct way to create a String.Index parameter for the remove(at:) function. One way might be to try something like this:
for y in anagram2.characters.enumerated() {
// `y` now represents a `tuple`: (offset: Int, element: Character)
// so, you don't need `counter` anymore; use `offset` instead
if i == y.element { //`i` is a Character, so it can compare to `element`
...
let yIndex: String.Index = anagram2.index(anagram2.startIndex, offsetBy: y.offset)
anagram2.remove(at: yIndex)
...
}
}
However, there are other issues with your code that will cause further errors.
For one, you're looping through a string (anagram2) and trying to change it at the same time - not a good thing to do.
Good luck to you in solving the anagram problem!
Thanks for the help Leo but I found a way of doing it :)
if anagram1.count == anagram2.count {
for i in anagram1.characters{
if (!failure){
counter = -1
failure = true
for y in anagram2.characters {
counter += 1
if i == y {
failure = false
if counter < anagram2.count {
anagram2.remove(at: (anagram2.index(anagram2.startIndex, offsetBy: counter)))
break;
}
}
}
}

How to loop through an array from the second element in elegant way using Swift

I am a beginner of Swift for iOS development, and want to find an elegant way to loop through an array from the second element.
If it is in Objective-C, I think I can write the code like below.
if(int i = 1; i < array.count; i++){
array[i].blablabla....
}
And I have tried several methods to do this in swift
for i in 1..<array.count {
array[i].blablabla....
}
But in this way, if the array's count is zero, the app will crash, because it cannot create an array from 1 to 0.
I also tried code like this, but it seems that it cannot enumerate from the second element.
for (index, object) in array.enumerate() {
object.blablabla...
}
So what's the best way to do this, do you guys have any idea?
You can use your array indices combined with dropfirst:
let array = [1,2,3,4,5]
for index in array.indices.dropFirst() {
print(array[index])
}
or simply:
for element in array.dropFirst() {
print(element)
}
you can also use custom subscript to make sure you avoid crashing
extension Array {
subscript (gurd index: Index) -> Array.Element? {
return (index < self.count && index >= 0) ? self[index] : nil
}
}
use example:
let myarray = ["a","b","c","d","e","f","g","h"]
for i in -15...myarray.count+20{
print(myarray[gurd: i])
}
In general, there is a nice pattern for getting a sub-array like this:
let array = [1, 2, 3, 4, 5]
array[2..<array.count].forEach {
print($0) // Prints 3, 4, 5 (array from index 2 to index count-1)
}
Leo's answer is more succinct and a better approach for the case you asked about (starting with the second element). This example starts with the third element and just shows a more general pattern for getting arbitrary ranges of elements from an array and performing an operation on them.
However, you still need to explicitly check to make sure you are using a valid range to avoid crashing as you noted, e.g.:
if array.count > 1 {
array[2..<array.count].forEach {
print($0)
}
}
if array.count>=2 {
for n in 1...array.count-1{
print(array[n]) //will print from second element forward
}
}
You could use array.dropFirts(), but if you need to iterate starting from 2nd element or 3rd I would do something like this:
for (index, item) in array.enumerated() where index > 1 {
item.blablabla()
}

Is there another way to handle exception in Range Operator in swift?

I am pretty much new to swift so, please bear with me :)
what if, in the following code
for i in (1...(self.count - 1)) { //(self.count is number of elements in Array Extension)
print(i)
}
self.count becomes 1.
well.. i can always work like the following
if self.count > 1
{
for i in (1...(self.count - 1)) {
print(i)
}
}
else
{
for i in ((self.count - 1)...1) {
print(i)
}
}
but is there another (probably better) way to deal with this ?
in which i won't have to use if-else control statements
PS :- as suggested in the comments, I have also used
for i in [(1...(self.count - 1))]
{
print(i)
}
but it still crashes when self.count = 1
To iterate over all array indices, use the ..< range operator which
omits the upper bound:
for i in 0 ..< array.count { }
or better:
for i in array.indices { }
The latter has the advantage that it works also for collections with
indices which are not zero-based (such as ArraySlice).
To iterate over all indices but the first,
for i in array.indices.dropFirst() { }
works in all cases, even if the array is empty. You should also check
if you really need the indices, or if you actually want to iterate
over the array elements, such as
for elem in array.dropFirst() { }
The best way to deal with range exceptions is to just not cause them
in the first place ;)
Why not just use a zero based index and then adjust it later in the loop if needed?
extension Array {
func oneBasedIndices() -> [Int] {
return (0..<count).map { return $0 + 1 }
}
}
The smallest number Array's count member can ever be is 0 and 0..<0 will never fail, so maybe something like this extension would do the trick if what you want is 1 based indices.

Is the for loop condition evaluated each loop in Swift?

I have a small debate at work: Is it a good practice to calculate the size of the Array in swift before running over it's items? What would be a better code practice:
Option A:
func setAllToFalse() {
for (var i = 0; i < mKeyboardTypesArray.count; i++ ) {
self.mKeyboardTypesArray[i] = false
}
}
or Option B:
func setAllToFalse() {
let typesCount = mKeyboardTypesArray.count
for (var i = 0; i < typesCount; i++ ) {
self.mKeyboardTypesArray[i] = false
}
}
All, of course, if I don't alter the Array during the loops.
I did go over the documentation, which states this:
The loop is executed as follows:
When the loop is first entered, the initialization expression is
evaluated once, to set up any constants or variables that are needed
for the loop. The condition expression is evaluated. If it evaluates
to false, the loop ends, and code execution continues after the for
loop’s closing brace (}). If the expression evaluates to true, code
execution continues by executing the statements inside the braces.
After all statements are executed, the increment expression is
evaluated. It might increase or decrease the value of a counter, or
set one of the initialized variables to a new value based on the
outcome of the statements. After the increment expression has been
evaluated, execution returns to step 2, and the condition expression
is evaluated again.
The idiomatic way to say this in Swift is:
func setAllToFalse() {
mKeyboardTypesArray = mKeyboardTypesArray.map {_ in false}
}
That way, there is nothing to evaluate and nothing to count.
In fact, this would make a nice Array method:
extension Array {
mutating func setAllTo(newValue:T) {
self = self.map {_ in newValue}
}
}
Now you can just say:
mKeyboardTypesArray.setAllTo(false)
Alternatively, you could do it this way (this involves taking the count, but only once):
mKeyboardTypesArray = Array(count:mKeyboardTypesArray.count, repeatedValue:false)
The loop condition is evaluated each time through the loop. Consider this experiment:
extension Array {
var myCount: Int {
get {
println("here")
return self.count
}
}
}
let a = [1, 2, 3, 4, 5]
for var i = 0; i < a.myCount; i++ {
println(a[i])
}
Output:
here 1 here 2 here 3 here 4 here 5 here
You could see a small speed improvement from Option B, but I expect that the count property on Array is not expensive if the array is unchanged. It is potentially a good code practice anyway because it communicates to the reader that you expect the array size to remain constant for duration of the loop.
It is possible that the compiler would optimize array.count by detecting that nothing in the loop modifies the array, but it wouldn't be able to do that for array.myCount because of the println side effect.
I have found that it is not, which can cause a crash if you're iterating through an array and removing items (for example). In current Swift syntax, this for loop
for i in 0..<m_pendingCommands.count
{
if m_pendingCommands[i].packetID < command.packetID
{
m_pendingCommands.remove(at: i)
}
}
crashed halfway through the array, with a bad index.
I switched this to a while loop:
var i: Int = 0
while i < m_pendingCommands.count
{
if m_pendingCommands[i].packetID < ID
{
m_pendingCommands.remove(at: i)
}
else
{
i += 1
}
}