Reputation: 1257
Swift 4.2
I have multiple functions that replace an object or struct in an array if it exists, and if it does not exist, it adds it.
func updateFruit(_ fruit: Fruit)
{
if let idx = fruitArray.firstIndex(where: { $0.id == fruit.id })
{
fruitArray[idx] = fruit
}
else
{
fruitArray.append(fruit)
}
}
Obviously I could make this into extension on Array:
extension Array
{
mutating func replaceOrAppend(_ item: Element, whereFirstIndex predicate: (Element) -> Bool)
{
if let idx = self.firstIndex(where: predicate)
{
self[idx] = item
}
else
{
append(item)
}
}
}
However, is there a simpler, easier way of expressing this? Preferably using a closure or build-in function.
NOTE: current implementation does not allow using a set.
Upvotes: 3
Views: 6813
Reputation: 299355
Given your use case, in which you're always checking $0.<prop> == newthing.<prop>
, you can lift this a little more by adding:
mutating func replaceOrAppend<Value>(_ item: Element,
firstMatchingKeyPath keyPath: KeyPath<Element, Value>)
where Value: Equatable
{
let itemValue = item[keyPath: keyPath]
replaceOrAppend(item, whereFirstIndex: { $0[keyPath: keyPath] == itemValue })
}
You can then use it like:
struct Student {
let id: Int
let name: String
}
let alice0 = Student(id: 0, name: "alice")
let alice1 = Student(id: 1, name: "alice")
let bob = Student(id: 0, name: "bob")
var array = [alice0]
array.replaceOrAppend(alice1, firstMatchingKeyPath: \.name) // [alice1]
array.replaceOrAppend(bob, firstMatchingKeyPath: \.name) // [alice1, bob]
And of course if you do this a lot, you can keep lifting and lifting.
protocol Identifiable {
var id: Int { get }
}
extension Student: Identifiable {}
extension Array where Element: Identifiable {
mutating func replaceOrAppendFirstMatchingID(_ item: Element)
{
replaceOrAppend(item, firstMatchingKeyPath: \.id)
}
}
array.replaceOrAppendFirstMatchingID(alice0) // [alice1, alice0]
Upvotes: 4
Reputation: 7746
Assuming your Types are Equatable, this is a generic extension:
extension RangeReplaceableCollection where Element: Equatable {
mutating func addOrReplace(_ element: Element) {
if let index = self.firstIndex(of: element) {
self.replaceSubrange(index...index, with: [element])
}
else {
self.append(element)
}
}
}
Though, keep in mind my (and your) function will only replace one of matching items.
Full Working playground test:
Upvotes: 1
Reputation: 2829
I can suggest to create protocol
Replacable
with replaceValue
that will represent identifier which we can use to enumerate thru objects.
protocol Replacable {
var replaceValue: Int { get }
}
now we can create extension to Array
, but now we can drop predicate from example code like this
extension Array where Element: Replacable {
mutating func replaceOrAppend(_ item: Element) {
if let idx = self.firstIndex(where: { $0.replaceValue == item.replaceValue }) {
self[idx] = item
}
else {
append(item)
}
}
}
Since Set
is not ordered collection, we can simply remove object if set contains it and insert new value
extension Set where Element: Replacable {
mutating func replaceOrAppend(_ item: Element) {
if let existItem = self.first(where: { $0.replaceValue == item.replaceValue }) {
self.remove(existItem)
}
self.insert(item)
}
}
Upvotes: 3