What's the Cleanest Way of Applying Map() to a Dictionary in Swift

What's the cleanest way of applying map() to a dictionary in Swift?

Swift 4+

Good news! Swift 4 includes a mapValues(_:) method which constructs a copy of a dictionary with the same keys, but different values. It also includes a filter(_:) overload which returns a Dictionary, and init(uniqueKeysWithValues:) and init(_:uniquingKeysWith:) initializers to create a Dictionary from an arbitrary sequence of tuples. That means that, if you want to change both the keys and values, you can say something like:

let newDict = Dictionary(uniqueKeysWithValues:
oldDict.map { key, value in (key.uppercased(), value.lowercased()) })

There are also new APIs for merging dictionaries together, substituting a default value for missing elements, grouping values (converting a collection into a dictionary of arrays, keyed by the result of mapping the collection over some function), and more.

During discussion of the proposal, SE-0165, that introduced these features, I brought up this Stack Overflow answer several times, and I think the sheer number of upvotes helped demonstrate the demand. So thanks for your help making Swift better!

How to use map to transform a dictionary in Swift?

There’s no direct way to map the values in a dictionary to create a new dictionary.

You can pass a dictionary itself into map, since it conforms to SequenceType. In the case of dictionaries, their element is a key/value pair:

// double all the values, leaving key unchanged
map(someDict) { (k,v) in (k, v*2) }

But this will result in an array of pairs, rather than a new dictionary. But if you extend Dictionary to have an initializer that takes a sequence of key/value pairs, you could then use this to create a new dictionary:

extension Dictionary {
init<S: SequenceType where S.Generator.Element == Element>
(_ seq: S) {
self.init()
for (k,v) in seq {
self[k] = v
}
}
}

let mappedDict = Dictionary(map(someDict) { (k,v) in (k, v*2) })
// mappedDict will be [“a”:2, “b”:4, “c”:6]

The other downside of this is that, if you alter the keys, you cannot guarantee the dictionary will not throw away certain values (because they keys may be mapped to duplicates of each other).

But if you are only operating on values, you could define a version of map on the values only of the dictionary, in a similar fashion, and that is guaranteed to maintain the same number of entries (since the keys do not change):

extension Dictionary {
func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
return Dictionary<Key,T>(zip(self.keys, self.values.map(transform)))
}
}

let mappedDict = someDict.mapValues { $0 * 2 }

In theory, this means you could do an in-place transformation. However, this is not generally a good practice, it’s better to leave it as creating a new dictionary, which you could always assign to the old variable (note, there’s no version of map on arrays that does in-place alteration, unlike say sort/sorted).

Map dictionary with values-arrays to another dictionary with values-arrays in swift?

let's say we have the following [String: [Int]]:

var dict = ["k1":[-1, -2, 1, 2]]

and we want to remap the dictionary removing all the negative elements in the array:

dict = dict.mapValues { v in v.filter { $0 > 0} }
print(dict) // ["k1":[1,2]]

Swift - Reducing a Dictionary of Arrays to a single array of same type using map/reduce/flatmap

All you need is a single flatMap:

let result = dict.flatMap { _, values in values }

Swift dictionary map - init in closure

Does your value for the dictionary need to be an optional? In a dictionary, when you assign its key as nil, the entry is deleted.

var params = [String:String?]()
params["lat"] = "40"
params["lon"] = "100"
params["key"] = "hey"
print(params) //result: ["lat": Optional("40"), "lon": Optional("100"), "key": Optional("hey")]
params["key"] = nil
print(params) //result: ["lat": Optional("40"), "lon": Optional("100")]

I suggest using a non optional-value dictionary. I have successfully written the code below:

import UIKit

var params = [String:String]()
params["lat"] = "40"
params["lon"] = "100"
let nsurl = params.map() {NSURLQueryItem.init(name: $0, value: $1)}
print(nsurl)
//Result:
//[<NSURLQueryItem 0x7f8252d29730> {name = lat, value = 40}, <NSURLQueryItem 0x7f8252d29700> {name = lon, value = 100}]

I hope this helps



Related Topics



Leave a reply



Submit