Filtering, mapping, capacity, and more!
Dictionaries are one of Swift’s most commonly used types, so it’s worth taking a few minutes to get to know them a bit better – even discovering one or two new techniques can make a big difference to your projects.
In this article I want to introduce you to five dictionary methods that deserve to be used more. None of them are complicated, so you should be able to read this whole thing in under ten minutes.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
You should already be familiar with the map()
method of sequences (if not, see here), but it’s less useful with dictionaries because it’s possible you might create a duplicate dictionary key as part of your transformation.
As a result, dictionaries get a bonus method called mapValues()
: this transforms each value inside a dictionary, putting the transformed result into a new dictionary using the original keys. This gives us the transforming behavior of map()
without the risk of duplicate keys.
As an example, here’s a dictionary of strings and integers that store results from an exam:
let results = ["Meghan": 80, "Chris": 90, "Charlotte": 95]
If we wanted to format those values into strings, we could use mapValues()
like this:
let formattedResults = results.mapValues { "Score: \($0)" }
Once that runs, Meghan’s value will be “Score: 80” rather than just 80.
If you have two dictionaries of the same type and want to merge them into a single array, you might be tempted to try this:
let first = ["a": 1, "b": 2]
let second = ["c": 3]
let third = first + third
Sadly that doesn’t work, because Swift can’t tell whether it will result in duplicated keys. Instead, you should use the merge()
and merging()
methods – the former merges one dictionary with another in place, and the latter returns a new dictionary.
These methods are possible because they accept a second parameter: a closure that is able to resolve key clashes. This will be given the existing value and the value that would overwrite it, and you get to choose which one you want.
Here’s an example of creating one dictionary then merging another into it:
var hexColors1 = ["red": "#ff0000", "green": "#00ff00"]
let hexColors2 = ["blue": "#0000ff"]
hexColors1.merge(hexColors2) { (_, second) in second }
Using (_, second) in second
as the merge resolution closure means “when trying to write an existing value with a new one, always use the new one.”
Dictionaries have a marvelous initializer called Dictionary(grouping:)
, and its job is to convert a sequence into a dictionary based on any grouping you want.
As an example, here’s an array of popular Swift conferences:
let conferences = ["AltConf", "App Builders", "NSSpain"]
We could group that into a dictionary, using the first letter of each conference as the key and the conference names stored as an array:
let alphabetical = Dictionary(grouping: conferences) { $0.first! }
That will make alphabetical
equal to ["N": ["NSSpain"], "A": ["AltConf", "App Builders"]]
.
Alternatively, we could group the conferences based on the length of their names, like this:
let length = Dictionary(grouping: conferences) { $0.count }
That will give length
the value [7: ["AltConf", "NSSpain"], 12: ["App Builders"]]
.
Earlier versions of Swift used to make filtering dictionaries return an array of tuples, but this has been cleaned up since Swift 4.0.
Nowadays, filtering dictionaries works like you’d expect: your closure is passed the key and value for each element, and any you return true for is included in a resulting dictionary.
For example, this would filter our exam results dictionary so that it includes only students who scored 85 or over:
let passes = results.filter { key, value in
return value >= 85
}
Modifying dictionaries can be costly in Swift, particularly if you modify them repeatedly – you might end up reallocating RAM many times without realizing it.
If you know roughly how many items your dictionary needs – even a vague guess, really – then you should call reserveCapacity()
on it before you start adding items. This ensures that your dictionary has contiguous storage allocated for at least the requested number of items so that Swift won’t need to keep reallocating memory.
As an example, if we were asking users to enter in state capitals for the US, we might write code like this:
var stateCapitals = [String: String]()
stateCapitals.reserveCapacity(50)
That reserves enough space for the dictionary to hold 50 items, which will be exactly enough.
Note: This is a minimum amount – you can of course go over, at which point Swift will just reallocate RAM as needed. However, this method is worth using if you even have a vague idea of how much you need.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.