Remove duplicates from a multi-dimensional array - arrays

I wrote the following extension to remove duplicates from my Array.
extension Array where Element : Equatable{
func removeDups() -> [Element]{
var result = [Element]()
for element in self{
if !result.contains(element){
result.append(element)
}
}
return result
}
}
Linear Array
let linearArr = [1,2,2,3]
linearArr.removeDups() // [1,2,3] works well!
Multi dimensional Array
let multiDimArr : [[Int]] = [[1,2,3], [1,2,3], [1,2 ,4]]
multiDimArr.removeDups() // Error!
Type [Int] does not conform to Equatable
I read here. And the answer says Array comparisons using == should work. It doesn't work all the time:
Works
if (["1", "2"] == ["1", "2"]){
print("true")
}
Doesn't work
if ([1, 2] == [1, 2]){ // ERROR!
print("true")
}
Ambiguous use of operator '=='
This is peculiar. I can compare Array of Strings but can't compare Array of Ints.
I also saw this comment:
The reason myArray1 == myArray2 is that NSObject conforms to Equatable, calling -[equals:] to conduct the test
Not sure if the ☝️ comment is still valid.
To summarize:
Are arrays equatable? Can I compare them using ==
Why is it different for comparing Array of Strings vs. Array of Ints
How can I remove duplicates from a multi-dimensional array?
I'm currently working with Swift 4.0.2

Are arrays equatable? Can I compare them using ==
Prior to Swift 4.1, Array didn't conform Equatable. There was however an overload of == that compared two arrays with Equatable elements, which is what enabled this to compile:
if ["1", "2"] == ["1", "2"] { // using <T : Equatable>(lhs: [T], rhs: [T]) -> Bool
print("true")
}
However in Swift 4.1 (available with Xcode 9.3), Array<Element> now conforms to Equatable when its Element conforms to Equatable. This change is given in the changelog:
Swift 4.1
[...]
SE-0143 The standard library types Optional, Array, ArraySlice, ContiguousArray, and Dictionary now conform to the Equatable protocol when their element types conform to Equatable. This allows the == operator to compose (e.g., one can compare two values of type [Int : [Int?]?] with ==), as well as use various algorithms defined for Equatable element types, such as index(of:).
Your example with multiDimArr.removeDups() compiles and runs as expected in 4.1, yielding the result [[1, 2, 3], [1, 2, 4]].
In Swift 4.0.3, you could hack it by adding another overload of removeDups() for nested arrays:
extension Array {
func removeDups<T : Equatable>() -> [Element] where Element == [T] {
var result = [Element]()
for element in self{
if !result.contains(where: { element == $0 }) {
result.append(element)
}
}
return result
}
}
let multiDimArr = [[1, 2, 3], [1, 2, 3], [1, 2, 4]]
print(multiDimArr.removeDups()) // [[1, 2, 3], [1, 2, 4]]
This does unfortunately lead to some code duplication, but at least you'll be able to get rid of it when updating to 4.1.
The fact that this example doesn't compile in either 4.0.3 or 4.1:
if [1, 2] == [1, 2] { // error: Ambiguous use of operator '=='
print("true")
}
is due to the bug SR-5944 – the compiler is considering it to be ambiguous due to == overloads for IndexSet and IndexPath (both of which are ExpressibleByArrayLiteral). But Swift should default an array literal to Array though, resolving the ambiguity.
Saying either:
if [1, 2] as [Int] == [1, 2] {
print("true")
}
or not importing Foundation resolves the issue.
Finally, it's worth noting that the performance of removeDups() can be improved if the Element type is also Hashable, allowing it to run in linear, rather than quadratic time:
extension Array where Element : Hashable {
func removeDups() -> [Element] {
var uniquedElements = Set<Element>()
return filter { uniquedElements.insert($0).inserted }
}
}
Here we're using a set to store the elements that we've seen, omitting any that we've already inserted into it. This also allows us to use filter(_:), as #Alexander points out.
And in Swift 4.2, Array also conditionally conforms to Hashable when its Element is Hashable:
Swift 4.2
[...]
SE-0143 The standard library types Optional, Array, ArraySlice, ContiguousArray, Dictionary, DictionaryLiteral, Range, and ClosedRange now conform to the Hashable protocol when their element or bound types (as the case may be) conform to Hashable. This makes synthesized Hashable implementations available for types that include stored properties of these types.

This is a place where recursion would solve the problem. Have you considered recursion? I was going to answer the question with actual code, but I don't know the syntax for Swift. so, here is some pesudocode:
function removeDupes() {
buffer = array;
foreach this->elements as key => element {
if(element is type array) {
this->elements[key] = element->removeDupes();
} else {
if(!this->contains(element)) {
buffer->add(element);
}
}
}
return buffer;
}
Basically, you want to test if the element itself is an array. If it is, then you want to call that array's removeDupes() method (which in turn will look for duplicates, unless it finds another array, then it will call itself again).

Related

Is Swift array reversed()[n] efficient or not?

When you call reversed() on an array in Swift, you get a ReverseCollection which merely wraps the original array with reversed access. Thus this is extremely efficient:
let arr = [1,2,3,4]
for i in arr.reversed() { print(i) }
Nothing actually got reversed except the access; the time complexity of reversed here is O(1). Cool!
But when I index into reversed() by an integer and check Quick Help, it appears I've lost all that efficiency; I'm shown the Sequence reversed() which generates a new array:
let arr = [1,2,3,4]
let i = arr.reversed()[1] // ???? this is a different `reversed()`!
And this seems to be true, because a reversed() array does not, itself, support indexing by number:
let arr = [1,2,3,4]
let rev = arr.reversed()
let i = rev[1] // compile error!
So my question is: is it really true that indexing by number into a reversed() array, as in my second example, loses the efficiency of the ReverseCollection index reversal?
Yes, indexing by Int is causing you to lose your O(1) access into the reversed array. Quite the gotcha!
As you note, reversed() here is an overloaded method; on Array specifically, you have two definitions to choose from:
BidirectionalCollection.reversed(), which returns a ReversedCollection, and
Sequence.reversed(), which turns any sequence into a reversed [Element]
The overloading here is most confusing for Array itself, because it's the only Sequence type such that type(of: x) == type(of: x.reversed()).
The Swift type checker prefers more specific overloads over less-specific ones, so in general, the compiler will use the BidirectionalCollection overload instead of the Sequence one where possible. The rub: BidirectionalCollection has an opaque index type, and cannot be indexed using an Int; when you do index into the collection with an Int, the compiler is instead forced to choose the Sequence overload over the BidirectionalCollection one. This is also why your second code sample fails to compile: Swift code inference does not take into account surrounding context on other lines; on its own, rev is preferred to be a ReversedCollection<Array<Int>>, so attempting to index into it with an Int fails.
You can see this a little more clearly with the following:
func collType1<T: Collection>(_: T) {
print(T.self) // ReversedCollection<Array<Int>>
print(T.Index.self) // Index
}
func collType2<T: Collection>(_: T) where T.Index == Int {
print(T.self) // Array<Int>
print(T.Index.self) // Int
}
let x: [Int] = [1, 2, 3]
collType1(x.reversed())
collType2(x.reversed())
Lest you wonder whether the compiler can optimize around this when the fact of Int-based indexing appears to not have any other side effects, at the time of writing, the answer appears to be "no". The Godbolt output is a bit too long to reproduce here, but at the moment, comparing
func foo1(_ array: [Int]) {
if array.reversed()[100] > 42 {
print("Wow!")
}
}
with
func foo2(_ array: [Int]) {
if array.reversed().dropFirst(100).first! > 42 {
print("Wow!")
}
}
with optimizations enabled shows foo2 performing direct array access
cmp qword ptr [rdi + 8*rax + 24], 43
having optimized away the ReversedCollection wrapper entirely, while foo1 goes through significantly more indirection.
Ferber explained the reason very well.
Here's an ad-hoc solution (which may not be preferred by everyone, because we are extending types from the standard library):
// RandomAccessCollection ensures fast index creation
extension ReversedCollection where Base: RandomAccessCollection {
subscript(_ offset: Int) -> Element {
let index = index(startIndex, offsetBy: offset)
return self[index]
}
}
[1, 2, 3].reversed()[0] // 3

"Cannot convert return expression" in flatMap with a meaningless expression inside

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]

2D Array extension Swift 3.1.1

I am trying to make an Array extension in Swift 3.1.1 that supports the addition of an object to a certain index in a 2D Array even if the array hasn't been populated yet. The extension should also provide the ability to get an object at certain indexPath. I have the code for this in Swift 2 but I don't seem to be able to migrate it to Swift 3. This is the Swift 2 code:
extension Array where Element: _ArrayProtocol, Element.Iterator.Element: Any {
mutating func addObject(_ anObject : Element.Iterator.Element, toSubarrayAtIndex idx : Int) {
while self.count <= idx {
let newSubArray = Element()
self.append(newSubArray)
}
var subArray = self[idx]
subArray.append(anObject)
}
func objectAtIndexPath(_ indexPath: IndexPath) -> Any {
let subArray = self[indexPath.section]
return subArray[indexPath.row] as Element.Iterator.Element
}
}
The code is taken from this answer.
As Martin says in his answer here, _ArrayProtocol is no longer public in Swift 3.1, therefore meaning that you cannot use it as a constraint in your extension.
A simple alternative in your case is to instead constrain the Array's Element to being a RangeReplaceableCollection – which both defines an init() requirement meaning "empty collection", and an append(_:) method in order to add elements to the collection.
extension Array where Element : RangeReplaceableCollection {
typealias InnerCollection = Element
typealias InnerElement = InnerCollection.Iterator.Element
mutating func fillingAppend(
_ newElement: InnerElement,
toSubCollectionAtIndex index: Index) {
if index >= count {
append(contentsOf: repeatElement(InnerCollection(), count: index + 1 - count))
}
self[index].append(newElement)
}
}
Note also that we're doing the append as a single call (using append(contentsOf:), ensuring that we only have to resize the outer array at most once.
For your method to get an element from a given IndexPath, you can just constrain the inner element type to being a Collection with an Int Index:
// could also make this an extension on Collection where the outer Index is also an Int.
extension Array where Element : Collection, Element.Index == Int {
subscript(indexPath indexPath: IndexPath) -> Element.Iterator.Element {
return self[indexPath.section][indexPath.row]
}
}
Note that I've made it a subscript rather than a method, as I feel it fits better with Array's API.
You can now simply use these extensions like so:
var arr = [[Int]]()
arr.fillingAppend(6, toSubCollectionAtIndex: 3)
print(arr) // [[], [], [], [6]]
let indexPath = IndexPath(row: 0, section: 3)
print(arr[indexPath: indexPath]) // 6
Although of course if you know the size of the outer array in advance, the fillingAppend(_:toSubCollectionAtIndex:) method is redundant, as you can just create your nested array by saying:
var arr = [[Int]](repeating: [], count: 5)
which will create an [[Int]] array containing 5 empty [Int] elements.
There's no need to limit all these ideas to the concrete Array type.
Here's my solution. This discussion was great in that I just learned about RangeReplaceableCollection. Merging (what I think is) the best of both worlds, I pushed all the operations down (up?) the Type hierarchy as far as possible.
Subscript works on much more than Array as #Hamish says. But also, there's no need to constrain the index type, so we have to get rid of IndexPath. We can always sugar this with typealias Index2d = ...
extension Collection where Self.Element: Collection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
}
}
Why not have a mutable version at the most generic possible level (between Collection and RangeReplaceableCollection) (unfortunately I don't think the getter can be inherited when we redefine subscript):
extension MutableCollection where Self.Element: MutableCollection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
set {
self[indexTuple.row][indexTuple.column] = newValue
}
}
}
Then, if you want to initialize lazily, avoid using init:repeatedValue and revise set to have auto-initialization semantics. You can trap bounds overflow and add missing empty elements in both dimensions by integrating the accepted answer's fillingAppend idea.
And when creating a 2D initializer, why not extend the idea of repeating in the natural way:
extension RangeReplaceableCollection where Element: RangeReplaceableCollection {
init(repeating repeatedVal: Element.Element, extents: (row: Int, column: Int)) {
let repeatingColumn = Element(repeating: repeatedVal, count: extents.column)
self.init(repeating: repeatingColumn, count: extents.row)
}
}
Example Usage:
enum Player {
case first
case second
}
class Model {
let playerGrid: Array<Array<Player>> = {
var p = [[Player]](repeating: .first, extents: (row: 10, column: 10))
p[(3, 4)] = .second
print("Player at 3, 4 is: \(p[(row: 3, column: 4)])")
return p
}()
}

Multiple errors with using multidimensional array of generics

I have done this in Objective-C but I can't do what I want to do in Swift.
I am trying to rotate a 2 dimensional array of any Type. I am using generics so that I could Strings & Ints or any other type.
import UIKit
let someArray = [[1,2,3],[7,8,9],[11,93,87]]
print(someArray[0])
func rotateArray<T> (array:[[T]]) ->Array{
var tempArray = [[T]]()
for i in 0..<array.count{
for j in 0..<array.count{
tempArray[j][array.count-i-1] = array[i][j]
}
}
return tempArray
}
someArray.count
let x = rotateArray(someArray)
However I get the following errors ( There could be other errors that I am not aware of), I also read this question and some others but couldn't relate to it.
reference to generic type 'Array' requires arguments in <..>
Binary Operator '..<' Cannot be applied to two 'Int' operands
Edit after fixing the initial two errors: fatal error: Index out of range
What are the things that I am doing wrong? Kindly include details, I am a complete noob.
You have written the return type -> Array, but since Array is generic, you need to specify what it contains, such as Array<Something> or equivalently [Something].
Seemingly, you want to return the same "shape"/type of array as the input, so you can use -> [[T]] for your return type.
(I'm not sure why the compiler produced an error about ..<, but it goes away if you fix the first issue.)
In addition to making your method return type [[T]], you have other problems here. You are instantiating tempArray (the array that will hold the arrays inside), but you are not instantiating those inner arrays. And you can't just use subscript operator, but rather you have to append to your respective arrays.
For example, if you want to rotate clockwise 90 degrees, it would be:
func rotateArray<T> (array:[[T]]) -> [[T]] {
var tempArray = [[T]]()
for column in 0 ..< array.first!.count {
var rowArray = [T]()
for row in (0 ..< array.count).reverse() {
rowArray.append(array[row][column])
}
tempArray.append(rowArray)
}
return tempArray
}
Thus
[[1, 2, 3], [7, 8, 9], [11, 93, 87]]
Becomes
[[11, 7, 1], [93, 8, 2], [87, 9, 3]]

Creating an extension to filter nils from an Array in Swift

I'm trying to write an extension to Array which will allow an array of optional T's to be transformed into an array of non-optional T's.
e.g. this could be written as a free function like this:
func removeAllNils(array: [T?]) -> [T] {
return array
.filter({ $0 != nil }) // remove nils, still a [T?]
.map({ $0! }) // convert each element from a T? to a T
}
But, I can't get this to work as an extension. I'm trying to tell the compiler that the extension only applies to Arrays of optional values. This is what I have so far:
extension Array {
func filterNils<U, T: Optional<U>>() -> [U] {
return filter({ $0 != nil }).map({ $0! })
}
}
(it doesn't compile!)
As of Swift 2.0, you don't need to write your own extension to filter nil values from an Array, you can use flatMap, which flattens the Array and filters nils:
let optionals : [String?] = ["a", "b", nil, "d"]
let nonOptionals = optionals.flatMap{$0}
print(nonOptionals)
Prints:
[a, b, d]
Note:
There are 2 flatMap functions:
One flatMap is used to remove non-nil values which is shown above. Refer - https://developer.apple.com/documentation/swift/sequence/2907182-flatmap
The other flatMap is used to concatenate results. Refer - https://developer.apple.com/documentation/swift/sequence/2905332-flatmap
TL;DR
Swift 4
Use array.compactMap { $0 }. Apple updated the framework so that it'll no longer cause bugs/confusion.
Swift 3
To avoid potential bugs/confusion, don't use array.flatMap { $0 } to remove nils; use an extension method such as array.removeNils() instead (implementation below, updated for Swift 3.0).
Although array.flatMap { $0 } works most of the time, there are several reasons to favor an array.removeNils() extension:
removeNils describes exactly what you want to do: remove nil values. Someone not familiar with flatMap would need to look it up, and, when they do look it up, if they pay close attention, they'll come to the same conclusion as my next point;
flatMap has two different implementations which do two entirely different things. Based off of type-checking, the compiler is going to decide which one is invoked. This can be very problematic in Swift, since type-inference is used heavily. (E.g. to determine the actual type of a variable, you may need to inspect multiple files.) A refactor could cause your app to invoke the wrong version of flatMap which could lead to difficult-to-find bugs.
Since there are two completely different functions, it makes understanding flatMap much more difficult since you can easily conflate the two.
flatMap can be called on non-optional arrays (e.g. [Int]), so if you refactor an array from [Int?] to [Int] you may accidentally leave behind flatMap { $0 } calls which the compiler won't warn you about. At best it'll simply return itself, at worst it'll cause the other implementation to be executed, potentially leading to bugs.
In Swift 3, if you don't explicitly cast the return type, the compiler will choose the wrong version, which causes unintended consequences. (See Swift 3 section below)
Finally, it slows down the compiler because the type-checking system needs to determine which of the overloaded functions to call.
To recap, there are two versions of the function in question both, unfortunately, named flatMap.
Flatten sequences by removing a nesting level (e.g. [[1, 2], [3]] -> [1, 2, 3])
public struct Array<Element> : RandomAccessCollection, MutableCollection {
/// Returns an array containing the concatenated results of calling the
/// given transformation with each element of this sequence.
///
/// Use this method to receive a single-level collection when your
/// transformation produces a sequence or collection for each element.
///
/// In this example, note the difference in the result of using `map` and
/// `flatMap` with a transformation that returns an array.
///
/// let numbers = [1, 2, 3, 4]
///
/// let mapped = numbers.map { Array(count: $0, repeatedValue: $0) }
/// // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
///
/// let flatMapped = numbers.flatMap { Array(count: $0, repeatedValue: $0) }
/// // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
///
/// In fact, `s.flatMap(transform)` is equivalent to
/// `Array(s.map(transform).joined())`.
///
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns a sequence or collection.
/// - Returns: The resulting flattened array.
///
/// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
/// and *n* is the length of the result.
/// - SeeAlso: `joined()`, `map(_:)`
public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
}
Remove elements from a sequence (e.g. [1, nil, 3] -> [1, 3])
public struct Array<Element> : RandomAccessCollection, MutableCollection {
/// Returns an array containing the non-`nil` results of calling the given
/// transformation with each element of this sequence.
///
/// Use this method to receive an array of nonoptional values when your
/// transformation produces an optional value.
///
/// In this example, note the difference in the result of using `map` and
/// `flatMap` with a transformation that returns an optional `Int` value.
///
/// let possibleNumbers = ["1", "2", "three", "///4///", "5"]
///
/// let mapped: [Int?] = numbers.map { str in Int(str) }
/// // [1, 2, nil, nil, 5]
///
/// let flatMapped: [Int] = numbers.flatMap { str in Int(str) }
/// // [1, 2, 5]
///
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns an optional value.
/// - Returns: An array of the non-`nil` results of calling `transform`
/// with each element of the sequence.
///
/// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
/// and *n* is the length of the result.
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
}
#2 is the one that people use to remove nils by passing { $0 } as transform. This works since the method performs a map, then filters out all nil elements.
You may be wondering "Why did Apple not rename #2 to removeNils()"? One thing to keep in mind is that using flatMap to remove nils is not the only usage of #2. In fact, since both versions take in a transform function, they can be much more powerful than those examples above.
For example, #1 could easily split an array of strings into individual characters (flatten) and capitalize each letter (map):
["abc", "d"].flatMap { $0.uppercaseString.characters } == ["A", "B", "C", "D"]
While number #2 could easily remove all even numbers (flatten) and multiply each number by -1 (map):
[1, 2, 3, 4, 5, 6].flatMap { ($0 % 2 == 0) ? nil : -$0 } == [-1, -3, -5]
(Note that this last example can cause Xcode 7.3 to spin for a very long time because there are no explicit types stated. Further proof as to why the methods should have different names.)
The real danger of blindly using flatMap { $0 } to remove nils comes not when you call it on [1, 2], but rather when you call it on something like [[1], [2]]. In the former case, it'll call invocation #2 harmlessly and return [1, 2]. In the latter case, you may think it would do the same (harmlessly return [[1], [2]] since there are no nil values), but it will actually return [1, 2] since it's using invocation #1.
The fact that flatMap { $0 } is used to remove nils seems to be more of Swift community recommendation rather than one coming from Apple. Perhaps if Apple notices this trend, they'll eventually provide a removeNils() function or something similar.
Until then, we're left with coming up with our own solution.
Solution
// Updated for Swift 3.0
protocol OptionalType {
associatedtype Wrapped
func map<U>(_ f: (Wrapped) throws -> U) rethrows -> U?
}
extension Optional: OptionalType {}
extension Sequence where Iterator.Element: OptionalType {
func removeNils() -> [Iterator.Element.Wrapped] {
var result: [Iterator.Element.Wrapped] = []
for element in self {
if let element = element.map({ $0 }) {
result.append(element)
}
}
return result
}
}
(Note: Don't get confused with element.map... it has nothing to do with the flatMap discussed in this post. It's using Optional's map function to get an optional type that can be unwrapped. If you omit this part, you'll get this syntax error: "error: initializer for conditional binding must have Optional type, not 'Self.Generator.Element'." For more information about how map() helps us, see this answer I wrote about adding an extension method on SequenceType to count non-nils.)
Usage
let a: [Int?] = [1, nil, 3]
a.removeNils() == [1, 3]
Example
var myArray: [Int?] = [1, nil, 2]
assert(myArray.flatMap { $0 } == [1, 2], "Flat map works great when it's acting on an array of optionals.")
assert(myArray.removeNils() == [1, 2])
var myOtherArray: [Int] = [1, 2]
assert(myOtherArray.flatMap { $0 } == [1, 2], "However, it can still be invoked on non-optional arrays.")
assert(myOtherArray.removeNils() == [1, 2]) // syntax error: type 'Int' does not conform to protocol 'OptionalType'
var myBenignArray: [[Int]?] = [[1], [2, 3], [4]]
assert(myBenignArray.flatMap { $0 } == [[1], [2, 3], [4]], "Which can be dangerous when used on nested SequenceTypes such as arrays.")
assert(myBenignArray.removeNils() == [[1], [2, 3], [4]])
var myDangerousArray: [[Int]] = [[1], [2, 3], [4]]
assert(myDangerousArray.flatMap { $0 } == [1, 2, 3, 4], "If you forget a single '?' from the type, you'll get a completely different function invocation.")
assert(myDangerousArray.removeNils() == [[1], [2, 3], [4]]) // syntax error: type '[Int]' does not conform to protocol 'OptionalType'
(Notice how on the last one, flatMap returns [1, 2, 3, 4] while removeNils() would have been expected to return [[1], [2, 3], [4]].)
The solution is similar to the answer #fabb linked to.
However, I made a few modifications:
I didn't name the method flatten, since there is already a flatten method for sequence types, and giving the same name to entirely different methods is what got us in this mess in the first place. Not to mention that it's much easier to misinterpret what flatten does than it is removeNils.
Rather than creating a new type T on OptionalType, it uses the same name that Optional uses (Wrapped).
Instead of performing map{}.filter{}.map{}, which leads to O(M + N) time, I loop through the array once.
Instead of using flatMap to go from Generator.Element to Generator.Element.Wrapped?, I use map. There's no need to return nil values inside the map function, so map will suffice. By avoiding the flatMap function, it's harder to conflate yet another (i.e. 3rd) method with the same name which has an entirely different function.
The one drawback to using removeNils vs. flatMap is that the type-checker may need a bit more hinting:
[1, nil, 3].flatMap { $0 } // works
[1, nil, 3].removeNils() // syntax error: type of expression is ambiguous without more context
// but it's not all bad, since flatMap can have similar problems when a variable is used:
let a = [1, nil, 3] // syntax error: type of expression is ambiguous without more context
a.flatMap { $0 }
a.removeNils()
I haven't looked into it much, but it seems you can add:
extension SequenceType {
func removeNils() -> Self {
return self
}
}
if you want to be able to call the method on arrays that contain non-optional elements. This could make a massive rename (e.g. flatMap { $0 } -> removeNils()) easier.
Assigning to self is different than assigning to a new variable?!
Take a look at the following code:
var a: [String?] = [nil, nil]
var b = a.flatMap{$0}
b // == []
a = a.flatMap{$0}
a // == [nil, nil]
Surprisingly, a = a.flatMap { $0 } does not remove nils when you assign it to a, but it does remove nils when you assign it to b! My guess is that this has something to do with the overloaded flatMap and Swift choosing the one we didn't mean to use.
You could temporarily resolve the problem by casting it to the expected type:
a = a.flatMap { $0 } as [String]
a // == []
But this can be easy to forget. Instead, I would recommend using the removeNils() method above.
Update
Seems like there's a proposal to deprecate at least one of the (3) overloads of flatMap: https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md
It's not possible to restrict the type defined for a generic struct or class - the array is designed to work with any type, so you cannot add a method that works for a subset of types. Type constraints can only be specified when declaring the generic type
The only way to achieve what you need is by creating either a global function or a static method - in the latter case:
extension Array {
static func filterNils(_ array: [Element?]) -> [Element] {
return array.filter { $0 != nil }.map { $0! }
}
}
var array:[Int?] = [1, nil, 2, 3, nil]
Array.filterNils(array)
Or simply use compactMap (previously flatMap), which can be used to remove all nil values:
[1, 2, nil, 4].compactMap { $0 } // Returns [1, 2, 4]
Since Swift 2.0 it is possible to add a method that works for a subset of types by using where clauses. As discussed in this Apple Forum Thread this can be used to filter out nil values of an array. Credits go to #nnnnnnnn and #SteveMcQwark.
As where clauses do not yet support generics (like Optional<T>), a workaround is necessary via a Protocol.
protocol OptionalType {
typealias T
func intoOptional() -> T?
}
extension Optional : OptionalType {
func intoOptional() -> T? {
return self.flatMap {$0}
}
}
extension SequenceType where Generator.Element: OptionalType {
func flatten() -> [Generator.Element.T] {
return self.map { $0.intoOptional() }
.filter { $0 != nil }
.map { $0! }
}
}
let mixed: [AnyObject?] = [1, "", nil, 3, nil, 4]
let nonnils = mixed.flatten() // 1, "", 3, 4
Swift 4
If you are fortunate enough to be using Swift 4 then you can filter out nil values using compactMap
array = array.compactMap { $0 }
E.g.
let array = [1, 2, nil, 4]
let nonNilArray = array.compactMap { $0 }
print(nonNilArray)
// [1, 2, 4]
Swift 4
This works with Swift 4:
protocol OptionalType {
associatedtype Wrapped
var optional: Wrapped? { get }
}
extension Optional: OptionalType {
var optional: Wrapped? { return self }
}
extension Sequence where Iterator.Element: OptionalType {
func removeNils() -> [Iterator.Element.Wrapped] {
return self.flatMap { $0.optional }
}
}
Test:
class UtilitiesTests: XCTestCase {
func testRemoveNils() {
let optionalString: String? = nil
let strings: [String?] = ["Foo", optionalString, "Bar", optionalString, "Baz"]
XCTAssert(strings.count == 5)
XCTAssert(strings.removeNils().count == 3)
let integers: [Int?] = [2, nil, 4, nil, nil, 5]
XCTAssert(integers.count == 6)
XCTAssert(integers.removeNils().count == 3)
}
}
Solution for Swift 5.3 and beyond
extension Array where Element == Any? {
/**
* Remove optionals from array
* ## Examples:
* Array.filterNils([2,nil,1,0]) // [2,1,0]
* let someArr: [Int?] = [2,nil,1,0]
* Array.filterNils(someArr) // [2,1,0]
*/
static func filterNils<T>(_ array: [T?]) -> [T] {
return array.compactMap { $0 }
}
}

Resources