Difference between flatMap and compactMap in Swift
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"]
Conclusion: 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 Optional
s so far was to introduce a new function called compactMap()
which will give you the expected result.
compactMap vs flatMap, Swift
There used to be two different functions named flatMap
for sequences; one to filter out nil values and one to join sequences. Now, one of these (the nil
variant) has been renamed to compactMap
Issue in understanding compactMap & flatMap
compactMap
should be used to filter out nil
elements from an array of Optional
s, while flatMap
can be used to flatten out a multi-dimensional array. However, you need to do both.
let final = myArray.flatMap{$0.compactMap{$0}}
print("Result:\(final)")
Map and flatMap difference in optional unwrapping in Swift 1.2
(Remark: The answer has been updated to reflect the syntax changes in Swift 3 and later, such as the abolishment of ImplicitlyUnwrappedOptional
.)
Optional.map()
and Optional.flatMap()
are declared as follows (I have omitted the throws/rethrows modifiers which are irrelevant here):
func map<U>(_ transform: (Wrapped) -> U) -> U?
func flatMap<U>(_ transform: (Wrapped) -> U?) -> U?
Let's consider a simplified version of your first example using “map”:
let number: Int? = 1
let res1 = number.map { $0 + 1 }
print(res1) // Optional(2)
number
has the type Int?
and the closure type is inferred as (Int) -> Int
. U
is Int
, and the type of the return value is Int?
. number
is not nil
, so it is unwrapped and passed 1
is passed to the closure. The closure returns 2
and map
returns Optional(2)
. If number
were nil
then the result would be nil
.
Now we consider a simplified version of your second example with “flatMap”:
let number: Int? = 1
let res2 = number.flatMap { $0 + 1 }
print(res2) // Optional(2)
flatMap
expects a closure of type (Wrapped) -> U?
, but { $0 + 1 }
does not return an optional. In order to make it compile, the compiler converts this to
let res2 = number.flatMap { return Optional($0 + 1) }
Now the closure has type (Int) -> Int?
, and U
is Int
again. Again, number
is unwrapped and passed to the closure. The closure returns Optional(2)
which is also the return value from flatMap
. If number
were nil
or if the closure would return nil
then the result would be nil
.
So there is indeed no difference between these invocations:
let res1 = number.map { $0 + 1 }
let res2 = number.flatMap { $0 + 1 }
However that is not what flatMap
is meant for. A more realistic example would be
func foo(_ s : String?) -> Int? {
return s.flatMap { Int($0) }
}
print(foo("1")) // Optional(1)
print(foo("x")) // nil (because `Int($0)` returns nil)
print(foo(nil)) // nil (because the argument is nil)
Generally, map
takes a closure of type (Wrapped) -> U
and transforms
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> Optional<U>.some(transform(wrapped))
flatMap
takes a closure of type (Wrapped) -> U?
and transforms
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> transform(wrapped)
Here transform(wrapped)
can be Optional<U>.none
as well.
If (as in your example) flatMap
is called with a closure which does not return an optional then the compiler converts it to an optional automatically, and there is no difference to map
anymore.
What is the best way to use map and sorted together in Swift
You can use flatMap(_:)
with replacingOccurrences(of:with:)
to ignore the nil result while converting string to number and then sort the result array.
let array = ["1,8", "3,5", "2,5", "4"]
let sortedArray = array.flatMap({ Double($0.replacingOccurrences(of: ",", with: ".")) }).sorted()
print(sortedArray) //[1.8, 2.5, 3.5, 4.0]
Note: If your Swift version is 4.1 or greater than use compactMap(_:)
because flatMap
is deprecated in Swift 4.1.
let sortedArray = array.compactMap({ Double($0.replacingOccurrences(of: ",", with: ".")) }).sorted()
Swift 4.1 compactMap for lazy collections
Yes, absolutely the same. They just decided to remove the ambiguity of flatMap
functions and renamed one of them to compactMap
not to confuse developers.
Here is the proposal: Introduce Sequence.compactMap(_:)
We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.
What is the Swift compiler doing with my return type? Is it somehow casting?
No, there is no casting going on. There are simply two different flatMap
functions being called. LazyMapSequence
has two flatMap(_:)
functions (well, technically four, but two are deprecated).
In your first code block, this function is inferred (because this version of flatMap
has a return type that matches your allRegions
function's return type):
func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
And in your second code block, this function is inferred (because there is no type annotation on your local variable that's forcing it to choose the above version of flatMap
):
func flatMap<SegmentOfResult>(_ transform: @escaping (Element) -> SegmentOfResult) -> LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Base, Element>, SegmentOfResult>>> where SegmentOfResult : Sequence
Swift array in array. Using compactMap / map instead of nested loops
I finally got it right:
if !activeDeals.isEmpty {
let bookings = activeDeals
.map { $0.bookings } // Gets the arrays of bookings
.compactMap { $0 } // Gets rid of the nils
.flatMap { $0 } // Flattens it
let carSelectionViewItem: [CarSelectionViewItem] = bookings.map {
let make = $0.vehicle?.make ?? ""
let model = $0.vehicle?.model ?? ""
let title = "\(make) \(model)"
return CarSelectionViewItem(icon: $0.vehicle?.carImage ?? "",
title: title,
description: String(format: "car_pending_booked_description".locale,
getNewDateStringFormat(string: $0.startDate)),
bookingStatus: .active,
dealStatus: .won,
dealId: $0.id ?? "",
showMoreFunc: {})
}
viewItems.append(contentsOf: carSelectionViewItem)
}
Related Topics
Swift - Resolving a Math Operation in a String
Load a Uiview from Nib in Swift
Overriding Superclass Property With Different Type in Swift
How to Get Ip Address in Swift
How to Add Type Constraints to a Swift Protocol Conformance Extension
How to Use Special Character in Nsurl
Why Is Swift Compile Time So Slow
How to Create a Global Variable
Using Decodable in Swift 4 With Inheritance
Accessing an Enumeration Association Value in Swift
Swift: How to Add a Protocol Extension to a Protocol
Swift - Extra Argument in Call
Constant Unassigned Optional Will Not Be Nil by Default
Swift Variable Name With ' (Backtick)
Deletable Table With Textfield on Swiftui