13

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

5
  • 1
    Note that you could use first(where:) rather than filter(_:) and first. Commented Jan 15, 2017 at 19:58
  • 2
    Structs are value type. After changing the property you have to assign the item back to the array. But don't fight the framework! If you need reference type, use a class. Commented Jan 15, 2017 at 20:00
  • Take a look at this: stackoverflow.com/a/26375105/312312 Commented Jan 15, 2017 at 20:06
  • 1
    Is an Array an appropriate choice in this case? Seems like a Dictionary mapping identifiers to Instruments would be a good choice Commented Jan 15, 2017 at 20:11
  • Ok, looks like I need to switch to dictionary and also use reference type. Thanks Commented Jan 15, 2017 at 20:50

2 Answers 2

31

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)]
Sign up to request clarification or add additional context in comments.

2 Comments

instruments.index is now instruments.firstIndex
this is the best answer, its all about the value type
3

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.