- Hands-On Design Patterns with Swift
- Florent Vilmart Giordano Scalzo Sergio De Simone
- 614字
- 2021-07-02 14:45:11
Iterating, mapping, and reducing
Dictionaries are collections; literally, Dictionary conforms to the collection type. Therefore, it gains lots of the features of collections.
The element type of the [Key: Value] collection is a (Key, Value) tuple.
Let's take a look at the different syntax, as follows:
for (key, value) in swiftReleases {
print("\(key) -> \(value)")
}
// Output:
2016-09-13 -> Swift 3.0
2015-09-21 -> Swift 2.0
2017-09-19 -> Swift 4.0
2015-04-08 -> Swift 1.2
2014-09-09 -> Swift 1.0
2014-10-22 -> Swift 1.1
2018-03-29 -> Swift 4.1
- Using forEach: This is very similar to using the for in pattern:
swiftReleases.forEach { (key, value) in
print("\(key) -> \(value)")
}
Technically, you can pass any function or closure in the execution block, which can make your code clearer, by extracting the implementation of the iterator outside.
- Using enumerated(): You can also get an enumerator from your dictionaries, if you need to get the index of the current key/value pair:
swiftReleases.enumerated().forEach { (offset, keyValue) in
let (key, value) = keyValue
print("[\(offset)] \(key) -> \(value)")
}
// Output:
[0] 2016-09-13 -> Swift 3.0
[1] 2015-09-21 -> Swift 2.0
[2] 2017-09-19 -> Swift 4.0
[3] 2015-04-08 -> Swift 1.2
[4] 2014-09-09 -> Swift 1.0
[5] 2014-10-22 -> Swift 1.1
[6] 2018-03-29 -> Swift 4.1
- Mapping values: You can easily transform values from a dictionary to the same type, or to another type completely. This is done with the mapValues method. Our Swift releases dictionary is raw data, and we'll probably want to parse the version in a proper semantic versioning major, minor, patch tuple. First let's declare Version as typealias, as it's very simple, and it's unlikely that we'll need it to be struct or class, at this point:
typealias Version = (major: Int, minor: Int, patch: Int)
Now, we need a simple method, which will transform a string version into a proper Version. Because not all strings are valid versions, we mark the method as throws, to encompass the cases where the string is invalid:
func parse(version value: String) throws -> Version {
// Parse the string 'Swift 1.0.1' -> (1, 0, 1)
fatalError("Provide implementation")
}
Finally, we'll apply the mapping to our dictionary object:
let releases: [String: Version] = try swiftReleases.mapValues { (value) -> Version in
try parse(version: value)
}
You can also use a shorter syntax, such as the following:
let releases = try swiftReleases.mapValues(parse(version:))
Or, you could use the following:
let releases = try swiftReleases.mapValues(parse)
- Mapping keys with a reducer: Transforming values is very easy with the mapValues method, but Swift doesn't provide a mapKeys method to transform the keys into other types or use other values for them. This is where the reduce method comes into play. We can use a reducer to transform our releases into another dictionary of the type, [Date: Version], as follows:
let releases: [String: Version] = ... // the mapped values from previous examples
let versionsByDate = try releases.reduce(into: [Date: Version]()) { (result, keyValue) in
let formatter = // NSDateFormatter...
if let date = formatter.date(from: keyValue.key) {
result[date] = keyValue.value
} else {
throw InvalidDateError()
}
}
assert(versionsByDate is [Date: Version])
We have now fully, and safely converted our original dictionary of strings into valid Swift objects, upon which we can perform more complex operations; these objects are more suited for handling in your programs. You will often find yourself transforming data from one type to another, so remember the map, reduce, and mapValues methods.