Why does compactMap return a nil result?
This is because [a, b]
is considered a [Any]
. When the element types in an array literal are entirely unrelated (Int?
and String?
), the array type is inferred to be [Any]
.
In the closure passed to compactMap
, you returned $0
, which is of type Any
. This means that $0
can never be nil
. All the optionals inside the array are all wrapped in an Any
the moment you put them in the array. Because you never return a nil
in the closure, all the elements stay in the result array.
The compiler can warn you about wrapping optionals in non-optional Any
s:
var a: String? = "abc"
let any: Any = a // warning!
But unfortunately it doesn't warn you when you create arrays.
Anyway, you can get the expected behaviour by specifying that you want a [Any?]
:
let result = ([a, b] as [Any?]).compactMap { $0 }
So you kind of unwrap them from Any
.
Or:
let result = [a as Any?, b as Any?].compactMap { $0 }
Why can an optional type be wrapped inside an
Any
?
According to the docs (In the Type Casting for Any
and AnyObject
section):
Any
can represent an instance of any type at all, including function types.
Thus, Optional<T>
undoubtedly can be represented by Any
.
Why can compactMap not remove nil values from an array of optional Any?
Firstly, change the type of arrayOfChildrenValues
to [Any]
, so it is no longer optional, since \.value
doesn't return an optional.
Now - you want to see if the element of type Any
is an optional. To do this, you can try match it to Optional<Any>.none
.
Change the following parts:
let arrayOfChildrenValues: [Any] = arrayOfChildren.map(\.value)
let compactArrayOfChildrenValues: [Any] = arrayOfChildrenValues.filter { element in
switch element {
case Optional<Any>.none: return false
default: return true
}
}
Other ways
Here is another way:
let arrayOfChildrenValues: [Any] = arrayOfChildren.map(\.value)
let compactArrayOfChildrenValues: [Any] = (arrayOfChildrenValues as [Any?]).compactMap { $0 }
However, there is another method with some strange behaviour happening, I'm not sure I can fully understand.
Here's the code:
let arrayOfChildrenValues: [Any?] = arrayOfChildren.map(\.value)
let compactArrayOfChildrenValues: [Any] = arrayOfChildrenValues.compactMap { $0 }
The reason why this is strange is because all the other methods work with arrayOfChildrenValues
being expressed like { element in element.value }
, however this only works when using key-paths. Perhaps it's because the return type is inferred as [Any?]
, so it does a cast similar to the example above?
Why does your other example work?
The reason why compactMap
doesn't work on [Any]
is because a value of type Any
cannot be not nil
, since it is not an optional.
You can test this, by trying compactMap
on [Any]
instead of [Any?]
. Modify your other example, as so:
let anyArray: [Any] = array.map { $0 as Any }
print(anyArray.compactMap({ element in element }))
Notice this prints [nil, Optional("Hello, world!"), nil]
- and has also wrapped all our values in an optional because in reality this is still of type [Any?]
, 'disguised' as [Any]
.
CompactMap to filter objects that have nil properties
You can still use Array.compactMap
With if-statement
anotherObject.array.compactMap { object in
if let property = object.property {
return Object(property: property)
}
return nil
}
With guard statement
anotherObject.array.compactMap {
guard let property = $0.property else { return nil }
return Object(property: property)
}
ternary operator example
anotherObject.array.compactMap { object in
object.property == nil ? nil : Object(property: object.property!)
}
Swift Compact Map returning empty
In your DummyUser
model you are using failable initializer, so in case of wrong dictionary provided to init method it will return nil
.
compactMap
automatically automatically filters nil
's and that's the reason why your output is empty.
Looking at this piece of code:
let names = objects.map { $0["name"]!}
return objects.compactMap(DummyUser.init)
I would debug this variable called names
because it probably has wrong input for the DummyUser
initializer. It should be dictionary containing all of your DummyUser
parameters. You can also debug your failable initializer to see which of the parameter is missing.
Swift - if string is nil. don't add it to the array
You can try ( Swift 4 )
let arr = [img1,img2].compactMap{$0}.map{AlamofireSource(urlString:$0)!}
or
let arr = alamofireSource.compactMap{$0}
for Swift 3
let arr = alamofireSource.flatMap{$0}
compactMap behaves differently when storing in an optional variable
I submitted this behavior as a bug at bugs.swift.org, and it came back as "works as intended." I had to give the response some thought in order to find a way to explain it to you; I think this re-expresses it pretty accurately and clearly. Here we go!
To see what's going on here, let's write something like compactMap
ourselves. Pretend that compactMap
does three things:
Maps the original array through the given transform, which is expected to produce Optionals; in this particular example, it produces
Int?
elements.Filters out nils.
Force unwraps the Optionals (safe because there are now no nils).
So here's the "normal" behavior, decomposed into this way of understanding it:
let marks = ["86", "45", "thiry six", "76"]
let result = marks.map { element -> Int? in
return Int(element)
}.filter { element in
return element != nil
}.map { element in
return element!
}
Okay, but in your example, the cast to [Int?]
tells compactMap
to output Int?
, which means that its first map
must produce Int??
.
let result3 = marks.map { element -> Int?? in
return Int(element) // wrapped in extra Optional!
}.filter { element in
return element != nil
}.map { element in
return element!
}
So the first map
produces double-wrapped Optionals, namely Optional(Optional(86))
, Optional(Optional(45))
, Optional(nil)
, Optional(Optional(76))
.
None of those is nil
, so they all pass thru the filter, and then they are all unwrapped once to give the result you're printing out.
The Swift expert who responded to my report admitted that there is something counterintuitive about this, but it's the price we pay for the automatic behavior where assigning into an Optional performs automatic wrapping. In other words, you can say
let i : Int? = 1
because 1
is wrapped in an Optional for you on the way into the assignment. Your [Int?]
cast asks for the very same sort of behavior.
The workaround is to specify the transform's output type yourself, explicitly:
let result3 = marks.compactMap {element -> Int? in Int(element) }
That prevents the compiler from drawing its own conclusions about what the output type of the map function should be. Problem solved.
[You might also want to look at the WWDC 2020 video on type inference in Swift.]
Can I give back nil instead of a tuple?
Did you try this,
... { q -> (String?, GetUserQuestionsOut?) in
}
or
... { q -> (String, GetUserQuestionsOut)? in
}
on the other hand, maybe you could consider using struct(model) or typealias instead of tuple
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)")
compactMap result is of weird type
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 }
Two Returns with no condition in Swift
There is the first return, return elems.compactMap
, that returns the result of the compactMap
as the function result (array) then there are two returns inside the closure that returns the result of the mapping i.e what gets added to the array.
If the given value
is larger than nums.count / 3
then key
is returned and added to the array otherwise nil
is returned meaning nothing gets added to the array.
Related Topics
"This Class Is Not Key Value Coding-Compliant" Using Coreimage
Uimarkuptextprintformatter and MAC Catalyst
Why I Can Not Use Setvalue for Dictionary
Swift: Binary Operator '==' Cannot Be Applied to Operands of Type "Protocol"
How to Use Passed Parameters in Swift Setmethodcallhandler - Self.Methodname(Result: Result)
Enum for Buttonstyle in Swiftui
In App Purchase on MAC Catalyst Not Working
Arrayliteralconvertible: Just a Normal Protocol
Accessing Bundle of Main Application While Running Xctests
What Was The Reason for Swift Assignment Evaluation to Void
Location Access Request in iOS 11
How to Check If a Variable Is Nil
"The Requested Snapshot Version Is Too Old." Error in Firestore
How to Create a Pulse Effect on an Skspritenode
Does Cocoa Connection Binding to Nstoolbaritem Prevent Deinitializing
Is There Any Reasonable Way to Access The Contents of a Characterset