[swift-evolution] Dictionary Enhancements

Jonathan Hull jhull at gbis.com
Sun Feb 19 10:21:23 CST 2017

>>> Add Dictionary.mapValues to return a Dictionary (can be more efficiently implemented than composition as the storage layout remains the same).
>> +1000.  I have also been asking for this since the beginning.  I built my own version (and use it frequently), but as you say, the standard library can do it much more efficiently.
>> I would also like to see an in-place version as well.
>> One design detail. Even though it is only mapping the values, I would like it to pass the key to the closure as well.  It occasionally figures in to the mapping logic.
> Do you have any examples you can share where you use the key in this type of map? I'm not contesting its usefulness; it would just be helpful to see some real-world usage.

I use mapValues mostly for simple transformations. Calling .lowercased on Strings so that they are consistent or lightening/darkening colors.  

Where the key comes in is when the key is actually gives context to the value (i.e. the value only makes sense in the context of the key).  This isn’t the prettiest example, but I have some animation code where I store some values defining the animation details in a dictionary keyed by the view to be animated.  Then I can run down each view and check if it needs setup/etc… based on those details. If I need to update those details based on the current state of the view, I need access to the associated view in map.  Embarrassingly messy.  I would most likely refactor before releasing the code publicly, but it does work.

I would be ok with a simpler mapValues as long as a version that can change the key is also available somewhere. That would cover any of my use cases (and I would just keep the key the same).

I actually almost use the key transform more often (though I still like mapValues for it’s simplicity when that is what is needed).  I transform string keys to be uniform (lowercased & removing diacritics).  For example I have a parser which has a built-in color keyword that can match color names and push the associated color on the stack.  The programmer can set a [String:UIColor] dictionary of names/colors. I run through and normalize the names so that the normalized user input will match.

>>> Please reply here with any comments or questions on the above list, or any additions you believe are important that are missing from it.
>> I would also like to see a version of map which returns a dictionary and handles key collisions:
>> 	let newDict = myDict.map(collision: {k,v1,v2 in v2}) { (k,v) in ... }
>> The collision parameter would take a throwing closure and handle the case of a key conflict (by returning the value to use, throwing, or trapping).  It would have a default value so that it would only have to be specified if a different behavior was desired.  
>> In advanced cases, the collision could be used to accumulate values together.  Because of this, I would actually like to see this on *collection* (not just dictionary).  The map closure is handed each element of the sequence (which in the case of dictionary is a key/value tuple), and expects a return value of a key/value tuple.  The collision block is called when a key is returned which has already been used to figure out what value to use.  This might choose a winner, or it could act like reduce, building a value from the components.
> I think the uses you're describing are handled by the merging initializer proposed in SE-100:
> 	https://github.com/apple/swift-evolution/blob/master/proposals/0100-add-sequence-based-init-and-merge-to-dictionary.md <https://github.com/apple/swift-evolution/blob/master/proposals/0100-add-sequence-based-init-and-merge-to-dictionary.md>
> For example, you can use a combining closure to select specific elements when the keys collide:
> let duplicates: DictionaryLiteral = ["a": 1, "b": 2, "a": 3, "b": 4]
> // Using the first value only
> Dictionary(merging: duplicates, combine: { (first, _) in first })    // ["b": 2, "a": 1]
> // Using the maximum value
> Dictionary(merging: duplicates, combine: max)      // ["b": 4, "a": 3]
> or to calculate the frequencies of values in a sequence:
> extension Sequence where Iterator.Element: Hashable {
>     func frequencies() -> [Iterator.Element: Int] {
>         return Dictionary(merging: self.lazy.map { v in (v, 1) }, combine: +)
>     }
> }
> [1, 2, 2, 3, 1, 2, 4, 5, 3, 2, 3, 1].frequencies()
> // [2: 4, 4: 1, 5: 1, 3: 3, 1: 3]
> Could you take a look and see if that provides the functionality you're looking for?

Almost.  From the document, the signature was slightly different.  There it only works on sequences of tuples (i.e. dictionaries). If the signature was changed to work with any sequence (as you show above), then it would meet my needs.

I do kind of like being able to call it as a method on sequence (the same way you call map on sequence), but that is just a matter of style. You could always build one form from the other...


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170219/780ebf5e/attachment.html>

More information about the swift-evolution mailing list