Changing The value of struct in an array

55,597

Solution 1

Besides what said by @MikeS, remember that structs are value types. So in the for loop:

for test in testings {

a copy of an array element is assigned to the test variable. Any change you make on it is restricted to the test variable, without doing any actual change to the array elements. It works for classes because they are reference types, hence the reference and not the value is copied to the test variable.

The proper way to do that is by using a for by index:

for index in 0..<testings.count {
    testings[index].value = 15
}

in this case you are accessing (and modifying) the actual struct element and not a copy of it.

Solution 2

Well I am going to update my answer for swift 3 compatibility.

When you are programming many you need to change some values of objects that are inside a collection. In this example we have an array of struct and given a condition we need to change the value of a specific object. This is a very common thing in any development day.

Instead of using an index to determine which object has to be modified I prefer to use an if condition, which IMHO is more common.

import Foundation

struct MyStruct: CustomDebugStringConvertible {
    var myValue:Int
    var debugDescription: String {
        return "struct is \(myValue)"
    }
}

let struct1 = MyStruct(myValue: 1)
let struct2 = MyStruct(myValue: 2)
let structArray = [struct1, struct2]

let newStructArray = structArray.map({ (myStruct) -> MyStruct in
    // You can check anything like:
    if myStruct.myValue == 1 {
        var modified = myStruct
        modified.myValue = 400
        return modified
    } else {
        return myStruct
    }
})

debugPrint(newStructArray)

Notice all the lets, this way of development is safer.

The classes are reference types, it's not needed to make a copy in order to change a value, like it happens with structs. Using the same example with classes:

class MyClass: CustomDebugStringConvertible {
    var myValue:Int

    init(myValue: Int){
        self.myValue = myValue
    }

    var debugDescription: String {
        return "class is \(myValue)"
    }
}

let class1 = MyClass(myValue: 1)
let class2 = MyClass(myValue: 2)
let classArray = [class1, class2]

let newClassArray = classArray.map({ (myClass) -> MyClass in
    // You can check anything like:
    if myClass.myValue == 1 {
        myClass.myValue = 400
    }
    return myClass
})

debugPrint(newClassArray)

Solution 3

To simplify working with value types in arrays you could use following extension (Swift 3):

extension Array {
    mutating func modifyForEach(_ body: (_ index: Index, _ element: inout Element) -> ()) {
        for index in indices {
            modifyElement(atIndex: index) { body(index, &$0) }
        }
    }

    mutating func modifyElement(atIndex index: Index, _ modifyElement: (_ element: inout Element) -> ()) {
        var element = self[index]
        modifyElement(&element)
        self[index] = element
    }
}

Example usage:

testings.modifyElement(atIndex: 0) { $0.value = 99 }
testings.modifyForEach { $1.value *= 2 }
testings.modifyForEach { $1.value = $0 }

Solution 4

How to change Array of Structs

for every element:

itemsArray.indices.forEach { itemsArray[$0].someValue = newValue }

for specific element:

itemsArray.indices.filter { itemsArray[$0].propertyToCompare == true }
                  .forEach { itemsArray[$0].someValue = newValue }

Solution 5

You have enough of good answers. I'll just tackle the question from a more generic angle.

As another example to better understand value types and what it means they get copied:

struct Item {
    var value:Int

}

func change (item: Item, with value: Int){
    item.value = value //  cannot assign to property: 'item' is a 'let' constant
}

That is because item is copied, when it comes in, it is immutable — as a convenience.

Had you made Item a class type then you were able to change its value.


var item2 = item1 // mutable COPY created
item2.value = 10
print(item2.value) // 10
print(item1.value) // 5
Share:
55,597

Related videos on Youtube

reza23
Author by

reza23

Updated on January 12, 2022

Comments

  • reza23
    reza23 over 2 years

    I want to store structs inside an array, access and change the values of the struct in a for loop.

    struct testing {
        var value:Int
    }
    
    var test1 = testing(value: 6 )
    
    test1.value = 2
    // this works with no issue
    
    var test2 = testing(value: 12 )
    
    var testings = [ test1, test2 ]
    
    for test in testings{
        test.value = 3
    // here I get the error:"Can not assign to 'value' in 'test'"
    }
    

    If I change the struct to class it works. Can anyone tell me how I can change the value of the struct.

    • mfaani
      mfaani over 7 years
      Also see value types vs class types Rob correctly says: if you pass a value type as a parameter to a method that then does something on another thread, you're essentially working with a copy of that value type. This ensures the integrity of that object passed to the method.
  • reza23
    reza23 over 9 years
    Surprisingly this also does not seem to work, and it seems to be changing the value of a copy. See my answer below
  • Antonio
    Antonio over 9 years
    You are forgetting that every time you pass a value type around, you pass a copy of it, not a reference. So the results you have are expected. testing is initialized with a copy of test1 and test2. Again, in var test1b = testings[0] you are creating a new copy of testings[0], which is itself a copy of test1
  • Antonio
    Antonio over 9 years
    See my comment in your answer - the obtained results are expected because you are still dealing with value types and not reference
  • Ian Warburton
    Ian Warburton over 7 years
    Isn't doing your "map" purer?
  • LightMan
    LightMan over 7 years
    @IanWarburton Sorry, but I don't understand that, what do you mean by "purer" ?
  • Ian Warburton
    Ian Warburton over 7 years
    I suppose it means more compliant with the functional programming paradigm.
  • Ian Warburton
    Ian Warburton over 7 years
    So, immutability being a functional concept, surely it makes sense to go with a map?
  • LightMan
    LightMan over 7 years
    Well this immutability has to do with how Swift works, in Swift structs are copied and class are passed by reference. So when you are changing a struct or passing it by argument you are making a new copy of it with the value changed. This has nothing to do with functional programming. Check the other answers, they all make copies of the array. Maps do the same thing and you can add conditions and new values while generating the new array. IMHO maps and the others are not exclusively used in functional programming, you can use them whenever you want.
  • Ian Warburton
    Ian Warburton over 7 years
    Passing by value is not the same as immutability. The question is specifically about not being able to mutate a value at all - not that they're changing a value in one place and it's not changing something elsewhere.
  • Womble
    Womble over 7 years
    In Swift 3, the 'var' specifier is not allowed. In the map closure, you need to copy the given struct, set the new struct's values, then return the new struct. Tedious.
  • mfaani
    mfaani over 4 years
    A very subtle difference between two seemingly similar for-loops
  • Prashant Tukadiya
    Prashant Tukadiya about 4 years
    How I don't know that :) Very nice
  • Martin Koles
    Martin Koles over 3 years
    Or you can write it like this: for (index, _) in testings.enumerated() { testings[index].value = 15 }
  • Bryan
    Bryan about 3 years
    @reza23 - Antonio's answer should be marked as the correct one so that folks looking at this in the future know how to handle it.