Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

  • 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

like image 787
Klemen Avatar asked Jan 15 '17 19:01

Klemen


People also ask

Can you change the value of a specific element in an array?

To change the value of a specific element of an array, we refer to its index number or position in the given array. Remember that in C++, the first character or element of a given object has its index position or number as 0 .

Can struct values be changed?

Unlike classes, a constant struct's properties cannot be changed—not from outside the struct, not even from within the struct's own methods, even if they're marked as mutating . Once a struct is constant, it is constant. It can't change.

How do you change a struct to an array?

C = struct2cell( S ) converts a structure into a cell array. The cell array C contains values copied from the fields of S . The struct2cell function does not return field names. To return the field names in a cell array, use the fieldnames function.

Can you modify struct?

Even though we defined x within the struct as a var property, we cannot change it, because origin is defined using let . This has some major advantages. For example, if you read a line like let point = ... , and you know that point is a struct variable, then you also know that it will never, ever, change.


2 Answers

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)]
like image 171
Owen Avatar answered Sep 28 '22 15:09

Owen


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
    }
}
like image 43
dfrib Avatar answered Sep 28 '22 17:09

dfrib