0

Say I have a map of keys to arrays of values and want to add a value to one of these arrays:

func addVal<K:Hashable, V>(map: inout [K: [V]], new: (key: K, val: V)) {
    if var list = map[new.key] {
        list.append(new.val)
    } else {
        map[new.key] = [new.val]
    }
}

This code won't work: since arrays have value semantics, list is a copy of map[new.key] and the new value never gets inserted into the stored array.

Is there a nice, idiomatic way to to this?

I am aware that this works perfectly:

func addVal<K:Hashable, V>(map: inout [K: [V]], new: (key: K, val: V)) {
    if map[new.key] != nil {
        map[new.key].append(new.val)
    } else {
        map[new.key] = [new.val]
    }
}

I'd consider this a non-nice workaround, though; I'd rather deal with optionals without checking explicitly for nil.

Raphael
  • 9,779
  • 5
  • 63
  • 94
  • Use context: [grouping arrays](http://stackoverflow.com/questions/41564580/group-elements-of-an-array-by-some-property/41566988#41566988). – Raphael Jan 10 '17 at 14:13
  • 1
    If you don't want the array to be copied, then see http://stackoverflow.com/questions/41079687/dictionary-in-swift-with-mutable-array-as-value-is-performing-very-slow-how-to. Based on your use case, also see http://stackoverflow.com/questions/31220002/how-to-group-by-the-elements-of-an-array-in-swift – Hamish Jan 10 '17 at 14:30

1 Answers1

1

This is safe, does not fiddle around with nil, and avoids unnecessary copying of the manipulated array:

func addVal<K:Hashable, V>(map: inout [K: [V]], new: (key: K, val: V)) {
    if let _ = map[new.key] {
        map[new.key]!.append(new.val) 
    } else {
        map[new.key] = [new.val]
    }
}
Raphael
  • 9,779
  • 5
  • 63
  • 94
Benjamin Lowry
  • 3,730
  • 1
  • 23
  • 27
  • Note that I believe this will still cause a copy of the mutated array due to the fact that `map[new.key]!.append(new.val)` will create a temporary variable of the array (now both the dictionary and temp variable have a view onto the array), append to that (causing copy on write), then insert it back into the dictionary. See the Q&A I linked to above for a way to avoid this copying. – Hamish Jan 10 '17 at 14:35
  • @Hamish Is that avoidable without directly editing the object passed through the function? It seems that this would be the case for all inout variables. – Benjamin Lowry Jan 10 '17 at 14:37
  • @BenjaminLowry Well it's avoidable by getting a unique view onto the array, which AFAIK you cannot do without temporarily removing the dictionary's view to it (i.e removing it from the dictionary, and then re-adding). But in most cases, this cost should be cheaper than copying the array. An `inout` parameter is treated as a pass-by-reference in most situations, so the dictionary itself won't be copied. – Hamish Jan 10 '17 at 17:01
  • @Hamish Is there any deep reason for that, or could the compiler be improved to skip copying in such cases? – Raphael Jan 10 '17 at 17:12
  • 1
    @Raphael (I don't think I'm going to do any better than Rob's reply to your comment on his post) I agree that mutating a dictionary's value shouldn't have to involve making a copy of it, but that's just what happens in this case as a result of how Dictionary and Array are implemented (both the dictionary's subscript getter and setter need to be called, and the array has no way of knowing the nature of the references to its underlying buffer, it just knows that it's being mutated while two things view it) and the fact that the compiler doesn't (yet) recognise this as a pattern to optimise. – Hamish Jan 10 '17 at 17:37