Array.filter with skipping of optional values

In a different projects written in Swift I've seen a snippet like that: [1, 2, nil, 3, nil, nil, 4].compactMap { $0 }.filter { $0 > 2 }.
Instead of that I prefer to use this extension:

compactFilter
extension Array {
    func compactFilter<ElementOfResult>(_ isIncluded: (ElementOfResult) throws -> Bool) rethrows -> [ElementOfResult] {
        return try self.compactMap { $0 as? ElementOfResult }.filter(isIncluded)
    }
}

But maybe there is the same function in the standard library? Please tell me if I should keep this extension in my Xcode 10/Swift 4.2 projects.

I don't think there is a method like that in the standard library.

You could flatMap the optional value inside the compactMap so you only iterate the array once. Not as elegant looking as compactFilter though.

[1, 2, nil, 3, nil, nil, 4].compactMap {
    $0.flatMap {element in
        element > 2 ? element : nil
    }
}

There is no need for a new method -- a single invocation of compactMap should do the trick:

let a: [Int?] = [1, 2, nil, 3, nil, nil, 4]
a.compactMap { value -> Int? in
    guard let value = value, value > 2 else { return nil } 
    return value
}
// ⟹ [3, 4]

Or, if you prefer terse constructs:

let a: [Int?] = [1, 2, nil, 3, nil, nil, 4]
a.compactMap { ($0 ?? 0) > 2 ? $0 : nil }
// ⟹ [3, 4]
2 Likes

@afarnham Thank you for your answer !!!
However I need to use this code snippet in more than 10 different places. That's why I want to wrap it into some extension of Array. Is it possible to replace implementation of the compactFilter with your solution?

(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)

Sure, you can simply define your own standalone function for this.

func compactFilter<S: Sequence, Element>(
  _ values: S, 
  _ predicate: (Element) throws -> Bool
) rethrows -> [Element]
where S.Element == Element? {
  return try values.compactMap { value in 
    guard let value = value, try predicate(value) else { return nil }
    return value
  }
}

let a: [Int?] = [1, 2, nil, 3, nil, nil, 4]
compactFilter(a) { $0 > 2 }
// ⟹ [3, 4]

It isn't currently possible to define this as an extension on any Sequence (or Array etc.) holding optional elements -- we'd need parameterized extensions for that:

extension<T> Sequence where Element == Optional<T> { // Not valid Swift (yet?)
  func compactFilter(...) {...}
}

However, if you only need it to work with a single concrete type (e.g., [Int?]), this works fine:

extension Array where Element == Int? {
  func compactFilter(_ predicate: (Int) throws -> Bool) rethrows -> [Int] {
    return try self.compactMap { value in 
      guard let value = value, try predicate(value) else { return nil }
      return value
    }
  }
}
a.compactFilter { $0 > 2 }
// ⟹ [3, 4]

2 Likes

@lorentey I need to use the compactFilter for different types, that's why I use generics in my version of the compactFilter .

Okay, so you'll need to define it as a standalone function.

Parameterized extensions are included in the generics manifesto but they aren't implemented yet.

2 Likes

For methods, you can put constraints on the method involving the type's parameters:

extension Array {
  func compactFilter<T>(_ predicate: (T) throws -> Bool) rethrows -> [T] where Element == T? {
    return try self.compactMap { value in 
      guard let value = value, try predicate(value) else { return nil }
      return value
    }
  }
}
5 Likes