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
As you can see, the order of keys is not preserved; this may affect your programs, if you expect the order to be consistent across runs. 
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.

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
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)
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.