4

What's the easiest/right way to update an item in an array? I want the caller to have the updated array as well. So:

static func updateItem(updatedItem: Item, inout items: [Item]) -> Bool {
        var item = items.filter{ $0.id == updatedItem.id }.first
        if item != nil {
            item = updatedItem
            return true
        }

        return false
    }

I want the caller to have the updated items (with the updated item). I think the problem with the above code is that it only updates the local variable item. What's the best way to actually update the relevant item inside the items array?

Prabhu
  • 11,837
  • 31
  • 115
  • 194

2 Answers2

5

You do it the same way Superman gets into his tights — one leg at a time. Cycle through the incoming inout array and replace any items where the id matches:

func updateItem(updatedItem: Item, items: inout [Item]) -> Bool {
    var result = false
    for ix in items.indices {
        if items[ix].id == updatedItem.id {
            items[ix] = updatedItem
            result = true
        }
    }
    return result
}

Note that this is Swift 3 syntax where inout precedes the type, not the label.

You can write it a little more "Swiftily" by using map:

func updateItem(updatedItem: Item, items: inout [Item]) {
    items = items.map {
        $0.id == updatedItem.id ? updatedItem : $0
    }
}

... but that amounts to the same thing in the end.

matt
  • 447,615
  • 74
  • 748
  • 977
  • I'm returning bool because the caller does some extra things depending on if item was found or not. How can I return false if item was not found in your "swiftily" approach? – Prabhu Dec 27 '16 at 22:49
  • I rewrote the first approach to return a Bool, and I suggest you use that. There is no savings in using the second approach — `map` is still a loop. – matt Dec 28 '16 at 01:43
2

You are mutating item which is merely a copy of the instance in the array (if Item is a value type, such as a struct, tuple, or enum), or a reference to it (if Item is a reference type, such as a `class). In either case, the array will be unaffected.

You'll need to find the index of the instance within the array, then mutate the array at that index.

func updateItem(updatedItem: Item, inout items: [Item]) -> Bool {
    guard let index = items.index(where: { $0.id == updatedItem.id }) else {
        return false // No matching item found
    }

    items[index] = updatedItem
    return true
}

This is all rather clunky, though. It would be better if you used a dictionary instead, mapping the id to the instance with that id. This means you'll have fast, constant time look up, and it'll be way more convenient. Here's how that will look:

// Assuming the "id" is an Int
func updateItem(updatedItem: Item, items: inout [Int: Item]) -> Bool {
    return items.updateValue(updatedItem, forKey: updatedItem.id) != nil
}
brandonscript
  • 57,554
  • 29
  • 142
  • 204
Alexander
  • 48,074
  • 8
  • 78
  • 121