Difference between flatMap and compactMap in Swift - swift

It seems like in Swift 4.1 flatMap is deprecated. However there is a new method in Swift 4.1 compactMap which is doing the same thing?
With flatMap you can transform each object in a collection, then remove any items that were nil.
Like flatMap
let array = ["1", "2", nil]
array.flatMap { $0 } // will return "1", "2"
Like compactMap
let array = ["1", "2", nil]
array.compactMap { $0 } // will return "1", "2"
compactMap is doing the same thing.
What are the differences between these 2 methods? Why did Apple decide to rename the method?

The Swift standard library defines 3 overloads for flatMap function:
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last overload function can be misused in two ways:
Consider the following struct and array:
struct Person {
var age: Int
var name: String
}
let people = [
Person(age: 21, name: "Osame"),
Person(age: 17, name: "Masoud"),
Person(age: 20, name: "Mehdi")
]
First Way: Additional Wrapping and Unwrapping:
If you needed to get an array of ages of persons included in people array you could use two functions :
let flatMappedAges = people.flatMap({$0.age}) // prints: [21, 17, 20]
let mappedAges = people.map({$0.age}) // prints: [21, 17, 20]
In this case the map function will do the job and there is no need to use flatMap, because both produce the same result. Besides, there is a useless wrapping and unwrapping process inside this use case of flatMap.(The closure parameter wraps its returned value with an Optional and the implementation of flatMap unwraps the Optional value before returning it)
Second Way - String conformance to Collection Protocol:
Think you need to get a list of persons' name from people array. You could use the following line :
let names = people.flatMap({$0.name})
If you were using a swift version prior to 4.0 you would get a transformed list of
["Osame", "Masoud", "Mehdi"]
but in newer versions String conforms to Collection protocol, So, your usage of flatMap() would match the first overload function instead of the third one and would give you a flattened result of your transformed values:
["O", "s", "a", "m", "e", "M", "a", "s", "o", "u", "d", "M", "e", "h", "d", "i"]
So, How did they solve it? They deprecated third overload of flatMap()
Because of these misuses, swift team has decided to deprecate the third overload to flatMap function. And their solution to the case where you need to to deal with Optionals so far was to introduce a new function called compactMap() which will give you the expected result.

There are three different variants of flatMap. The variant of Sequence.flatMap(_:) that accepts a closure returning an Optional value has been deprecated. Other variants of flatMap(_:) on both Sequence and Optional remain as is. The reason as explained in proposal document is because of the misuse.
Deprecated flatMap variant functionality is exactly the same under a new method compactMap.
See details here.

High-order function - is a function which operates by another function in arguments or/and returned. For example - sort, map, filter, reduce...
map vs compactMap vs flatMap
[RxJava Map vs FlatMap]
map - transform(Optional, Sequence, String)
flatMap - flat difficult structure into a single one(Optional, Collection)
compactMap - next step of flatMap. removes nil
flatMap vs compactMap
Before Swift v4.1 three realisations of flatMap had a place to be(without compactMap). That realisation were responsible for removing nil from a sequence. And it were more about map than flatMap
Experiments
//---------------------------------------
//map - for Optional, Sequence, String, Combine
//transform
//Optional
let mapOptional1: Int? = Optional(1).map { $0 } //Optional(1)
let mapOptional2: Int? = Optional(nil).map { $0 } //nil
let mapOptional3: Int?? = Optional(1).map { _ in nil } //Optional(nil)
let mapOptional4: Int?? = Optional(1).map { _ in Optional(nil) } //Optional(nil)
//collection
let mapCollection1: [Int] = [1, 2].map { $0 } //[1, 2]
let mapCollection2: [Int?] = [1, 2, nil, 4].map { $0 } //Optional(1), Optional(2), nil, Optional(4),
let mapCollection3: [Int?] = ["Hello", "1"].map { Int($0) } //[nil, Optional(1)]
//String
let mapString1: [Character] = "Alex".map { $0 } //["A", "l", "e", "x"]
//---------------------------------------
//flatMap - Optional, Collection, Combime
//Optional
let flatMapOptional1: Int? = Optional(1).flatMap { $0 } //Optional(1)
let flatMapOptional2: Int? = Optional(nil).flatMap { $0 } //nil
let flatMapOptional3: Int? = Optional(1).flatMap { _ in nil }
let flatMapOptional4: Int? = Optional(1).flatMap { _ in Optional(nil) }
//Collection
let flatMapCollection1: [Int] = [[1, 2], [3, 4]].flatMap { $0 } //[1, 2, 3, 4]
let flatMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].flatMap { $0 } //DEPRECATED(use compactMap): [[1, 2], [3, 4]]
let flatMapCollection3: [Int?] = [[1, nil, 2], [3, 4]].flatMap { $0 } //[Optional(1), nil, Optional(2), Optional(3), Optional(4)]
let flatMapCollection4: [Int] = [1, 2].flatMap { $0 } //DEPRECATED(use compactMap):[1, 2]
//---------------------------------------
//compactMap(one of flatMap before 4.1) - Array, Combine
//removes nil from the input array
//Collection
let compactMapCollection1: [Int] = [1, 2, nil, 4].compactMap { $0 } //[1, 2, 4]
let compactMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].compactMap { $0 } //[[1, 2], [3, 4]]
[Swift Optional map vs flatMap]
[Swift Functor, Applicative, Monad]

Related

Abort conversion when one of element fails in to convert in swift

In swift, we have higher order functions like map, filter, reduce and so on for array
But what if i have an array such as [Any] = [1, 2, 3, "1"].
and i wish to convert this array to an array of Int. But since there is "1" in the array, my logic is to accept this entire array invalid, which i will map to an empty array, let say.
How do i do it with higher functions in this case?
Filtering is easy
let array: [Any] = [1, 2, 3, "1"]
let filtered = array.compactMap{ $0 as? Int}
/// prints [1, 2, 3]
but i want the end result to be [], not [1, 2, 3].
How do i achieve that using higher order functions?
CompactMap
Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
So you can't return what you want by using the compactMap
let array: [Any] = [1, 2, 3, "1"]
for case let element in array {
if let _ = element as? Int {
continue
} else {
print("\(element) isn't an Int, so array is invalid")
break
}
}
Assuming the array as? [Int] ?? [] is inapplicable (e.g. you need to apply a more complex transformation), you can throw an error instead.
enum ConversionError: Error {
case notAllValuesWereInts
}
func convertOrThrow(_ input: Any) throws -> Int {
switch input {
case let int as Int: return int
default: throw ConversionError.notAllValuesWereInts
}
}
let array: [Any] = [1, 2, 3, "1"]
let ints: [Int]
do {
ints = try array.map(convertOrThrow)
} catch {
ints = []
}
print(ints)
In a case like this where the error is used for control flow, doesn't carry any useful information, you can use try? to simplify things:
let array: [Any] = [1, 2, 3, "1"]
let ints = (try? array.map(convertOrThrow)) ?? []
Though this should do the trick as you just want to check whether the array is an array of Integers or not
array as? [Int] ?? []
But in case you don't want to use above, you might be interested in the following code
let filtered = array.filter{($0.self as? Int) == nil}.count == 0 ? array as! [Int] : []
The above code yields [] if there is any element which is not Int

compactMap result is of weird type

I expect allMembers to be of type [Member]. But instead its type is [[Member]]. Why compactMap does not return result of [Member] type?
class Team {
let members = Array(repeating: Member(), count: 2)
}
class Member {
}
let teams = Array(repeating: Team(), count: 3)
let allMembers = teams.compactMap { $0.members }
You actually need flatMap, not compactMap.
Even though previously (before Swift 4.1), compactMap was also called flatMap, it had a different implementation and function signature than the current flatMap, since compactMap can be used instead of consecutive map and filter calls to map each element to a new element while only keeping non-nil elements. On the other hand, flatMap flattens out nested lists while mapping elements.
This is the still existing flatMap on Sequence, while this is the deprecated flatMap on Sequence that was renamed to compactMap. As you can see, the function signature of the renamed version was
func flatMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
, so its closure input argument returned an Optional value (just like compactMap does now), while the still existing flatMap has a function signature
func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
, which doesn't return an Optional in its closure.
You should use the non-deprecated flatMap to flatten out your nested Array<Array<Member>>.
let allMembers = teams.flatMap { $0.members }
Adding some detail in #David's answer:
Using flatMap on a sequence (like an Array) filtering anything that maps to nil is now deprecated and replaced by compactMap.
Using flatMap on a sequence with a closure that returns an optional.
Sequence.flatMap<U>(_ transform: (Element) -> U?) -> U?
For example:
Rather than:
let names: [String?] = ["Tom", nil, "Peter", nil, "Harry"]
let valid = names.flatMap { $0 }
You have to use:
let valid = names.compactMap { $0 }
// ["Tom", "Peter", "Harry"]
let names: [String?] = ["Tom", nil, "Peter", nil, "Harry"]
let counts = names.compactMap { $0?.count }
// [3, 5, 5]
But again, Swift 4.1 does not deprecate all uses of flatMap - out of 3 cases, only one case is changing. Below two situations are as it is where you need to use flatMap:
1) Using flatMap on a sequence with a closure that returns a sequence:
Sequence.flatMap<S>(_ transform: (Element) -> S) -> [S.Element] where S : Sequence
Example:
let scores = [[5,2,7], [4,8], [9,1,3]]
let allScores = scores.flatMap { $0 }
// [5, 2, 7, 4, 8, 9, 1, 3]
let passMarks = scores.flatMap { $0.filter { $0 > 5} }
// [7, 8, 9]
2) Using flatMap on an optional: The closure takes the non-nil value of the optional and returns an optional. If the original optional is nil then flatMap returns nil:
Optional.flatMap<U>(_ transform: (Wrapped) -> U?) -> U?
Example:
let input: Int? = Int("8")
let passMark: Int? = input.flatMap { $0 > 5 ? $0 : nil}
// Optional(8)
For more detail, please refer here

Unwrapping with flatMap

I want to get the weekday int of a date that I know exists like this:
let dayOfWeek = Calendar.current.dateComponents([.weekday], from: row.date).weekday
However, this returns an optional, and I'm trying to figure out how to avoid force unwrapping it.
My though was to do:
let dayOfWeek = (row.date).compactMap( { Calendar.current.dateComponents([.weekday], from: $0).weekday!
})
However this gives me the error "value of type 'Date' has no member 'compactMap'"
Can someone tell me what I'm doing wrong, or how I should go about fixing this?
No optionals are needed. It's simply:
let dayOfWeek = Calendar.current.component(.weekday, from: row.date)
Note, flatMap is not relevant here because row.date is not an optional. But even if it were, it's worth noting that the Optional method flatMap has not had its name changed. It's still flatMap. Only the Sequence method of this name has been changed to compactMap.
See Swift Evolution 0187: Introduce Sequence.compactMap(_:)
So, this is still flatMap:
let foo: Int? = 42
let bar: String? = foo.flatMap { i -> String in
return "The value is \(i)"
}
// Optional("The value is 42")
Note, the returned value is an optional String?. In your example, it looks like you're trying to use flatMap to unwrap your optional, but that's not what flatMap is for. It's for calling the closure if it can unwrap foo, but for returning nil if it can't unwrap it. So it just returns another optional (a String? in my above example).
The flatMap that has been renamed to compactMap in Swift 4.1 is the Sequence rendition:
let baz = ["1", "2", "x", "3"]
let qux: [Int] = baz.compactMap { string -> Int? in
return Int(string) // return integer value if it could convert it, return `nil` if not
}
// [1, 2, 3]
To make it even more confusing, there is still flatMap used with sequences:
let letters = ["a", "b", "c"]
let quux = letters.map { Array(repeating: $0, count: 3) }
// [["a", "a", "a"], ["b", "b", "b"], ["c", "c", "c"]]
let quuz = letters.flatMap { Array(repeating: $0, count: 3) }
// ["a", "a", "a", "b", "b", "b", "c", "c", "c"]
There is no need to avoid forced unwrapping in this case. While weekday is optional, it will never be nil when you specifically request the .weekday component.
let dayOfWeek = Calendar.current.dateComponents([.weekday], from: row.date).weekday!

Can somebody explain this change of behavior for flatMap between Swift 3 & 4? [duplicate]

I was examining .lazy for high order functions and have got some interesting compile errors related to flatMap function (and possibly others)
Examples
let array = [1, 2, 3, 4, 5, 6]
array
.flatMap {
print("DD")
return $0 // Cannot convert return expression of type 'Int' to return type 'String?'
}
.forEach {
print("SS")
print($0)
}
Commenting out a bit
array
.flatMap {
// print("DD")
return $0
}
.forEach {
print("SS")
print($0)
}
And everything works.. even more interesting example
array
.flatMap {
let z = $0
return $0 // Or return z - all is the same "Cannot convert return expression of type 'Int' to return type 'String?'"
}
.forEach {
print("SS")
print($0)
}
What could cause that behavior?
The flatMap(_:) method on Sequence currently (as of Swift 4) has two different meanings:
It can take a transform closure that returns an optional T?, and it will return a [T], filtering out the nil results (this overload is to be renamed to compactMap(_:) in a future version).
public func flatMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult]
It can take a transform closure that returns a Sequence, and it will return an array containing the concatenation of all the resulting sequences.
public func flatMap<SegmentOfResult : Sequence>(
_ transform: (Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element]
Now, in Swift 4, String became a RangeReplaceableCollection (and therefore a Sequence). So Swift 3 code that did this:
// returns ["foo"], as using the `nil` filtering flatMap, the elements in the closure
// are implicitly promoted to optional strings.
["foo"].flatMap { $0 }
now does this:
// returns ["f", "o", "o"], a [Character], as using the Sequence concatenation flatMap,
// as String is now a Sequence (compiler favours this overload as it avoids the implicit
// conversion from String to String?)
["foo"].flatMap { $0 }
To preserve source compatibility, specialised flatMap overloads were added for strings:
//===----------------------------------------------------------------------===//
// The following overloads of flatMap are carefully crafted to allow the code
// like the following:
// ["hello"].flatMap { $0 }
// return an array of strings without any type context in Swift 3 mode, at the
// same time allowing the following code snippet to compile:
// [0, 1].flatMap { x in
// if String(x) == "foo" { return "bar" } else { return nil }
// }
// Note that the second overload is declared on a more specific protocol.
// See: test/stdlib/StringFlatMap.swift for tests.
extension Sequence {
#_inlineable // FIXME(sil-serialize-all)
#available(swift, obsoleted: 4)
public func flatMap(
_ transform: (Element) throws -> String
) rethrows -> [String] {
return try map(transform)
}
}
extension Collection {
#_inlineable // FIXME(sil-serialize-all)
public func flatMap(
_ transform: (Element) throws -> String?
) rethrows -> [String] {
return try _flatMap(transform)
}
}
Such that the above usage would still return a [String] in Swift 3 compatibility mode, but a [Character] in Swift 4.
So, why does
let array = [1, 2, 3, 4, 5, 6]
array
.flatMap {
print("DD")
return $0 // Cannot convert return expression of type 'Int' to return type 'String?'
}
.forEach {
print("SS")
print($0)
}
tell you that the closure should return a String??
Well, Swift currently doesn't infer parameter and return types for multi-statement closures (see this Q&A for more info). So the flatMap(_:) overloads where the closure returns either a generic T? or a generic S : Sequence aren't eligible to be called without explicit type annotations, as they would require type inference to satisfy the generic placeholders.
Thus, the only overload that is eligible, is the special String source compatibility one, so the compiler is expecting the closure to return a String?.
To fix this, you can explicitly annotate the return type of the closure:
array
.flatMap { i -> Int? in
print("DD")
return i
}
.forEach {
print("SS")
print($0)
}
But if you're not actually using the optional filtering functionality of this flatMap(_:) overload in your real code, you should use map(_:) instead.
flatMap can have different meanings depending on the context. You might tell the compiler more precisely which one you are intending to use.
The two usual purposes to apply flatMap to an array which the compiler can infer is to
flatten nested arrays
let array = [[1, 2, 3, 4, 5, 6], [7, 8, 9]]
let flattened = array.flatMap{$0}
print(flattened) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
map to another type and filter optionals
let array = ["1", "a", "2", "3", "b", "4", "5", "6"]
let flattened = array.flatMap{ Int($0) }
print(flattened) // [1, 2, 3, 4, 5, 6]

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

I'd like to map a function on all keys in the dictionary. I was hoping something like the following would work, but filter cannot be applied to dictionary directly. What's the cleanest way of achieving this?
In this example, I'm trying to increment each value by 1. However this is incidental for the example - the main purpose is to figure out how to apply map() to a dictionary.
var d = ["foo" : 1, "bar" : 2]
d.map() {
$0.1 += 1
}
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!
With Swift 5, you can use one of the five following snippets in order to solve your problem.
#1. Using Dictionary mapValues(_:) method
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.mapValues { value in
return value + 1
}
//let newDictionary = dictionary.mapValues { $0 + 1 } // also works
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
#2. Using Dictionary map method and init(uniqueKeysWithValues:) initializer
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let tupleArray = dictionary.map { (key: String, value: Int) in
return (key, value + 1)
}
//let tupleArray = dictionary.map { ($0, $1 + 1) } // also works
let newDictionary = Dictionary(uniqueKeysWithValues: tupleArray)
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
#3. Using Dictionary reduce(_:_:) method or reduce(into:_:) method
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.reduce([:]) { (partialResult: [String: Int], tuple: (key: String, value: Int)) in
var result = partialResult
result[tuple.key] = tuple.value + 1
return result
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
result[tuple.key] = tuple.value + 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
#4. Using Dictionary subscript(_:default:) subscript
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
var newDictionary = [String: Int]()
for (key, value) in dictionary {
newDictionary[key, default: value] += 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
#5. Using Dictionary subscript(_:) subscript
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
var newDictionary = [String: Int]()
for (key, value) in dictionary {
newDictionary[key] = value + 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
While most of the answers here focus on how to map the entire dictionary (keys and values), the question really only wanted to map the values. This is an important distinction since mapping values allows you to guarantee the same number of entries, whereas mapping both key and value might result in duplicate keys.
Here’s an extension, mapValues, that allows you to map just the values. Note it also extends dictionary with an init from a sequence of key/value pairs, which is a bit more general than initializing it from an array:
extension Dictionary {
init<S: SequenceType where S.Generator.Element == Element>
(_ seq: S) {
self.init()
for (k,v) in seq {
self[k] = v
}
}
func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
return Dictionary<Key,T>(zip(self.keys, self.values.map(transform)))
}
}
The cleanest way is to just add map to Dictionary:
extension Dictionary {
mutating func map(transform: (key:KeyType, value:ValueType) -> (newValue:ValueType)) {
for key in self.keys {
var newValue = transform(key: key, value: self[key]!)
self.updateValue(newValue, forKey: key)
}
}
}
Checking that it works:
var dic = ["a": 50, "b": 60, "c": 70]
dic.map { $0.1 + 1 }
println(dic)
dic.map { (key, value) in
if key == "a" {
return value
} else {
return value * 2
}
}
println(dic)
Output:
[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
You can also use reduce instead of map. reduce is capable of doing anything map can do and more!
let oldDict = ["old1": 1, "old2":2]
let newDict = reduce(oldDict, [String:Int]()) { dict, pair in
var d = dict
d["new\(pair.1)"] = pair.1
return d
}
println(newDict) // ["new1": 1, "new2": 2]
It would be fairly easy to wrap this in an extension, but even without the extension it lets you do what you want with one function call.
Swift 5
map function of dictionary comes with this syntax.
dictData.map(transform: ((key: String, value: String)) throws -> T)
you can just set closure values to this
var dictData: [String: String] = [
"key_1": "test 1",
"key_2": "test 2",
"key_3": "test 3",
"key_4": "test 4",
"key_5": "test 5",
]
dictData.map { (key, value) in
// Operations on values or key
}
It may be give this warning Result of call to 'map' is unused
Then just set _ = before variable.
May be it will help you
Thank you.
It turns out you can do this. What you have to do is create an array from the MapCollectionView<Dictionary<KeyType, ValueType>, KeyType> returned from the dictionaries keys method. (Info here) You can then map this array, and pass the updated values back to the dictionary.
var dictionary = ["foo" : 1, "bar" : 2]
Array(dictionary.keys).map() {
dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}
dictionary
I was looking for a way to map a dictionary right into a typed Array with custom objects. Found the solution in this extension:
extension Dictionary {
func mapKeys<U> (transform: Key -> U) -> Array<U> {
var results: Array<U> = []
for k in self.keys {
results.append(transform(k))
}
return results
}
func mapValues<U> (transform: Value -> U) -> Array<U> {
var results: Array<U> = []
for v in self.values {
results.append(transform(v))
}
return results
}
func map<U> (transform: Value -> U) -> Array<U> {
return self.mapValues(transform)
}
func map<U> (transform: (Key, Value) -> U) -> Array<U> {
var results: Array<U> = []
for k in self.keys {
results.append(transform(k as Key, self[ k ]! as Value))
}
return results
}
func map<K: Hashable, V> (transform: (Key, Value) -> (K, V)) -> Dictionary<K, V> {
var results: Dictionary<K, V> = [:]
for k in self.keys {
if let value = self[ k ] {
let (u, w) = transform(k, value)
results.updateValue(w, forKey: u)
}
}
return results
}
}
Using it as followed:
self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
return VDLFilterValue(name: key, amount: value)
})
Swift 3
I try an easy way in Swift 3.
I want to map [String: String?] to [String : String], I use forEach instead of map or flat map.
let oldDict = ["key0": "val0", "key1": nil, "key1": "val2","key2": nil]
var newDict = [String: String]()
oldDict.forEach { (source: (key: String, value: String?)) in
if let value = source.value{
newDict[source.key] = value
}
}
According to the Swift Standard Library Reference, map is a function of arrays. Not for dictionaries.
But you could iterate your dictionary to modify the keys:
var d = ["foo" : 1, "bar" : 2]
for (name, key) in d {
d[name] = d[name]! + 1
}
I you're only trying to map the values (potentially changing their type), include this extension:
extension Dictionary {
func valuesMapped<T>(_ transform: (Value) -> T) -> [Key: T] {
var newDict = [Key: T]()
for (key, value) in self {
newDict[key] = transform(value)
}
return newDict
}
}
Given you have this dictionary:
let intsDict = ["One": 1, "Two": 2, "Three": 3]
Single-line value transformation then looks like this:
let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]
Multi-line value transformation then looks like this:
let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
let calculationResult = (value * 3 + 7) % 5
return String("Complex number #\(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...
Another approach is to map to a dictionary and reduce, where functions keyTransform and valueTransform are functions.
let dictionary = ["a": 1, "b": 2, "c": 3]
func keyTransform(key: String) -> Int {
return Int(key.unicodeScalars.first!.value)
}
func valueTransform(value: Int) -> String {
return String(value)
}
dictionary.map { (key, value) in
[keyTransform(key): valueTransform(value)]
}.reduce([Int:String]()) { memo, element in
var m = memo
for (k, v) in element {
m.updateValue(v, forKey: k)
}
return m
}
Swift 3 I used this,
func mapDict(dict:[String:Any])->[String:String]{
var updatedDict:[String:String] = [:]
for key in dict.keys{
if let value = dict[key]{
updatedDict[key] = String(describing: value)
}
}
return updatedDict
}
Usage:
let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)
Ref
Swift 3
Usage:
let value = ["a": "AAA", "b": "BBB", "c": "CCC"]
value.transformed { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]
Extension:
extension Dictionary {
func transformed(closure: (Key, Value) -> (Key, Value)?) -> [Key: Value] {
var dict = [Key: Value]()
for key in keys {
guard
let value = self[key],
let keyValue = closure(key, value)
else { continue }
dict[keyValue.0] = keyValue.1
}
return dict
}
}