One strange behaviour: I have several objects and arrays:
for image in images {
for nextID in image.parts {
if nextID.number != 0 {
if let n = primaryLookUp[nextID.number] {
image.parts[0].newID = 0
nextID.newID = 0 // cannot assign!!!
}
}
}
nextID is simply going through the array of .parts. Does ist become a "let" assignment so I couldn't change anything later on? The
image.parts[0].newID = 0
is valid!
I believe this is the explanation for what you are seeing:
The value of a loop variable is immutable, just as if it had been assigned with let.
In your first loop, images is an array of objects (defined by a class). Since a class object is a reference type, you can alter the fields of the object. Only the loop variable image in that case cannot be assigned.
In your second loop, image.parts is an array of structs. Since a struct is a value type, the entire nextID and its fields will be immutable within the loop.
If you add var to the second loop, you will be able to assign to nextID.newID:
for image in images {
for var nextID in image.parts {
if nextID.number != 0 {
if let n = primaryLookUp[nextID.number] {
image.parts[0].newID = 0
nextID.newID = 0 // this now works!!!
}
}
}
}
but, you are changing a copy of the nextID (since structures copies are by value) and you are not changing the nextID contained in the original image object.
Related
Why the function with increasing i value gives me an error:
Cannot pass immutable value to mutating operator: 'i' is a 'let'
constant
Here is my code:
import UIKit
/* initialising the struct to store values in array and change them
with function*/
struct DayMenu {
/* declaring the variables of menu for breakfast, lunch and dinner*/
let breakfast = "egg"
let lunch = ["orange", "apple", "meat"]
let dinner = ["veggi", "juice", "pasta"]
/* creating a function which changes the value by increasing the index
of the array by 1*/
func showMenu(_:String, _:String, _:String)->String {
var i:Int=0
for i in 0...dinner.count {
for i in 0...lunch.count {
return lunch[i]
dinner[i]
i++
/* increasing the index by 1*/
error Cannot pass immutable value to mutating operator: i is a 'let'
constant*/
}}}}
let dinnerForToday = DayMenu()
let whatToEat = dinnerForToday.showMenu
Use while instead of for
You cannot change i which is an iterator.
var i:Int = 0 is useless in your code
i++ is not used in Swift. Use i += 1
You use wrong i key for both loops. For instance, use i and j.
Sample:
//external loop
var i = 0
while i < dinner.count {
i += 1
//your logic
//internal loop
var j = 0
while j < lunch.count {
j += 1
//you logic
}
}
Sorry, your method showMenu is a mess.
You cannot increment the index variable of a for loop, that's what the error message says.
But the point is you must not increment the index variable of a for loop, because the for loop does it automatically.
If you need a different increment step as 1 use stride.
Other issues:
In the code the incremental line is not even reached at runtime, because you will return the first lunch while exiting the method.
The outer repeat loop is pointless
The three parameters, which have no parameters labels by the way are unused.
Your syntax for i in 0...dinner.count can cause an exception because for example in an array with one item the range is 0...1 (2 indexes), but there is only one item.
A return value at the end of the method is missing.
Your method compiles with this syntax
func showMenu() -> String {
for i in stride(from: 0, to: dinner.count, by: 2) {
for j in stride(from: 0, to: lunch.count, by: 2) {
return lunch[j]
// dinner[i]
}
}
return ""
}
However in practice the method does just this:
func showMenu() -> String {
return lunch[0]
}
The loop decrements the loop counter if it finds an element that shouldn't be there and removes it.
var iMax = numListViews
for var i = 0; i < numListViews; i += 1 {
if (columnsSortTypesArray[i] == "") {
columnsSortTypesArray.removeAtIndex(i)
i--
iMax--
} else {
listViews[i].sortList(columnsSortTypesArray[i])
}
}
You shouldn't have used a for loop for that purpose in the first place.
Modifying the loop variable within the loop is error-prone and a good
example why C-style for loops have been deprecated.
To remove empty strings from an array use filter():
columnsSortTypesArray = columnsSortTypesArray.filter { $0 != "" }
To populate another array based on this array, use map(), e.g.
listViews = columnsSortTypesArray.map { sortList($0) }
Also: If you have two (or more) arrays which always have to be kept
in sync, consider to define a struct WhatEver with two (or more) properties
and use a single array of these WhatEver elements instead.
I have array of arrays grid in my code. what I want to do is checking if there is a object at x, y let object = grid[x][y] if object is not nil I edit it else I assign a new object to it grid[x][y] = newObject().
if let object = grid[x][y] {
object.property = newValue
} else {
grid[x][y] = newObject()
}
but I get fatal error: Array index out of range in the line if let object = grid[x][y] {
what is the best way to do that?
Thanks in advance.
What you need to do (as I said in my comment) is to both allocate the array to the size that you want, and to make it an array of Object? rather than Object (or Object! - why do you do that?). Something like this, for a 2x2 array ...
var grid = [[Object?]](count:2, repeatedValue: [Object?](count:2, repeatedValue:nil))
Firstly, if you want to modify your grid object down the road, you can't define it with let. You must use var.
It looks like you're trying to use optional binding (if let x = Optional(y) { ... }) with array subscripting (array[x]). This won't work, as an array subscript doesn't return an optional. It'll instead return a valid object or throw an exception. You could try this:
if grid.count > x {
if grid[x].count > y {
object = grid[x][y]
}
}
var index: Int=0
for index in 1...3{
print(index)
}
print(index)//prints 0
If I run this code, the last print gives 0, which means the index inside the for-in is not the same as outside. Seems like this force declares a new constant.
I am looking for similar way to retain the last value in the sequence
I know I can do
var index_out: Int=0
for index in 1...3{
print(index)
index_out = index
}
print(index_out)
If you're gonna loop through something and want to know the end index just use the amount of times you looped through it:
let n = 3
for index in 1...n{
print(index)
}
print(n)
Or with an array:
let array = [Int](count: 10, repeatedValue: 2)
for index in 0..<array.count {
print(index)
}
print(array.count)
The way you know you can do it is how to do it, for loops create their own scope. Anything declared inside of a set of { } means that it's for use within that scope only.
You can also use the way for in is implemented with an outer scope generator and while:
var generator = sequence.generate()
var element = generator.next()
while let x = generator.next() {
element = x
}
// element is nil if the sequence is empty
print(element)
this is only another way to do this but personally I think you should avoid that.
A much nicer solution would be with reduce (in Swift 1.x: global function, in Swift 2: as method)
// with default value
let lastElement = sequence.reduce(default) { $1 }
// if default should be nil you have to provide the explicit type because it cannot be inferred; probably use an extension:
// as extension
extension SequenceType {
var last: Self.Generator.Element? {
return self.reduce(nil) { $1 }
}
}
There is no way to do it, since it is in different scope than your method. But you also SHOULD NOT do what you do, to assign to your index on each iteration - that is wasteful. Instead, just increase index after you are done:
var data = []
var lastIndex = 0
for (index, object) in enumerate(data) {
...
}
lastIndex += data.count
Or, if you need to break for cycle for some reason, just assign it from inside of the cycle just before you break.
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
}
}