How to change a value of struct that is in array?

11,871

Solution 1

With an array of struct Instrument, you can obtain the index for the Instrument with a particular identifier, and use that to access and modify a property of the Instrument.

struct Instrument {
    let identifier: String
    var value: Int
}

var instruments = [
    Instrument(identifier: "alpha", value: 3),
    Instrument(identifier: "beta", value: 9),
]

if let index = instruments.index(where: { $0.identifier == "alpha" }) {
    instruments[index].value *= 2
}

print(instruments) // [Instrument(identifier: "alpha", value: 6), Instrument(identifier: "beta", value: 9)]

Solution 2

If you stick to the value type approach (and given that the identifier is not unique: otherwise, consider using a dictionary for simple extract-and-replace logic), you could write a mutating function to the type which owns the [Instruments] array, which finds a (first) Instrument instance in the array and mutates it using a supplied closure. E.g. (thanks @Hamish for improvements!):

struct Instrument {
    let identifier: String
    var changeThis: Int
    init(_ identifier: String, _ changeThis: Int) {
        self.identifier = identifier
        self.changeThis = changeThis
    }
}

struct Foo {
    var instruments: [Instrument]

    @discardableResult // do not necessarily make use of the return result (no warning if not)
    mutating func updateInstrument(forFirst identifier: String,
            using mutate: (inout Instrument) -> ()) -> Bool {
        if let idx = instruments.indices
            .first(where: { instruments[$0].identifier == identifier }) {

            // mutate this instrument (in-place) using supplied closure
            mutate(&instruments[idx])

            return true // replacement successful
        }
        return false // didn't find such an instrument
    }
}

Example usage:

var foo = Foo(instruments:
    [Instrument("a", 1), Instrument("b", 2),
     Instrument("c", 3), Instrument("b", 4)])

// make use of result of call
if foo.updateInstrument(forFirst: "b", using: { $0.changeThis = 42 }) {
    print("Successfully mutated an instrument")
} // Successfully mutated an instrument

// just attempt mutate and discard the result
foo.updateInstrument(forFirst: "c", using: { $0.changeThis = 99 })

print(foo.instruments)
/* [Instrument(identifier: "a", changeThis: 1), 
    Instrument(identifier: "b", changeThis: 42), 
    Instrument(identifier: "c", changeThis: 99),
    Instrument(identifier: "b", changeThis: 4)] */

As shown in @Owen:s answer, an even neater approach to finding the first index for a certain predicate on the element is using the index(where:) method of array (rather than indices.first(where:) as used above). Using the index(where:) approach in the complete example above would simply correspond to replacing

if let idx = instruments.indices
    .first(where: { instruments[$0].identifier == identifier }) { ...

with

if let idx = instruments
    .index(where: { $0.identifier == identifier }) { ...

in the updateInstrument(forFirst:using) method of Foo.

We could further condense the updateInstrument(forFirst:using) method by applying the map function of Optional to perform the (possible) replacement and boolean return in a single line:

struct Foo {
    var instruments: [Instrument]

    @discardableResult
    mutating func updateInstrument(forFirst identifier: String,
        using mutate: (inout Instrument) -> ()) -> Bool {
        return instruments
            .index(where: { $0.identifier == identifier })
            .map { mutate(&instruments[$0]) } != nil
    }
}
Share:
11,871
Klemen
Author by

Klemen

An iOS Developer with a sense and drive to make apps simple, appealing and convenient to use. Keeping myself sharp by practicing long walks, meditation and exchanging words with other human beings. Always on the look for new and exciting opportunities, in Slovenia, abroad or remote.

Updated on June 05, 2022

Comments

  • Klemen
    Klemen almost 2 years

    I'm using swift for my project.

    I have an array of structs named Instrument. Later on I made a function that returns specific Instrument from array. Then I wanted to change value on one of its property, but this change is not reflected inside the array.

    I need to have this array to include all the changes on the elements inside. What do you think is the best practice here?

    • Change Instrument from struct to class.
    • Somehow rewrite the function that returns Instrument from array.

    Right now I use this function:

    func instrument(for identifier: String) -> Instrument? {
      if let instrument = instruments.filter({ $0.identifier == identifier }).first {
        return instrument
      }
      return nil
    }
    

    I start with the struct because swift is known to be language for structs and I want to learn when to use struct of class.

    thanks

  • Anil CR
    Anil CR almost 4 years
    instruments.index is now instruments.firstIndex
  • ssowri1
    ssowri1 almost 4 years
    Keen response.!