Klemen
Klemen

Reputation: 2182

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

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?

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

Upvotes: 13

Views: 14347

Answers (2)

dfrib
dfrib

Reputation: 73186

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
    }
}

Upvotes: 3

Owen
Owen

Reputation: 371

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)]

Upvotes: 31

Related Questions