创建扩展以从 Swift 中的数组过滤 nils
Creating an extension to filter nils from an Array in Swift
我正在尝试编写 Array 的扩展,它将允许将可选 T 的数组转换为非可选 T 的数组。
例如这可以写成这样的自由函数:
func removeAllNils(array: [T?]) -> [T] {
return array
.filter({ [=11=] != nil }) // remove nils, still a [T?]
.map({ [=11=]! }) // convert each element from a T? to a T
}
但是,我无法将其作为扩展程序使用。我试图告诉编译器扩展仅适用于可选值数组。这是我目前所拥有的:
extension Array {
func filterNils<U, T: Optional<U>>() -> [U] {
return filter({ [=12=] != nil }).map({ [=12=]! })
}
}
(无法编译!)
无法限制为泛型结构或 class 定义的类型 - 数组设计用于任何类型,因此您不能添加适用于类型子集的方法。只能在声明泛型类型时指定类型约束
实现所需功能的唯一方法是创建全局函数或静态方法 - 在后一种情况下:
extension Array {
static func filterNils(_ array: [Element?]) -> [Element] {
return array.filter { [=10=] != nil }.map { [=10=]! }
}
}
var array:[Int?] = [1, nil, 2, 3, nil]
Array.filterNils(array)
或者简单地使用compactMap
(以前是flatMap
),它可以用来删除所有的nil值:
[1, 2, nil, 4].compactMap { [=11=] } // Returns [1, 2, 4]
从 Swift 2.0 开始,可以使用 where
子句添加适用于类型子集的方法。正如本 Apple Forum Thread 中所讨论的,这可用于过滤掉数组的 nil
值。感谢@nnnnnnnn 和@SteveMcQwark。
由于 where
子句尚不支持泛型(如 Optional<T>
),因此需要通过协议来解决。
protocol OptionalType {
typealias T
func intoOptional() -> T?
}
extension Optional : OptionalType {
func intoOptional() -> T? {
return self.flatMap {[=10=]}
}
}
extension SequenceType where Generator.Element: OptionalType {
func flatten() -> [Generator.Element.T] {
return self.map { [=10=].intoOptional() }
.filter { [=10=] != nil }
.map { [=10=]! }
}
}
let mixed: [AnyObject?] = [1, "", nil, 3, nil, 4]
let nonnils = mixed.flatten() // 1, "", 3, 4
从 Swift 2.0 开始,您不需要编写自己的扩展来过滤数组中的 nil 值,您可以使用 flatMap
,它会展平数组并过滤 nils:
let optionals : [String?] = ["a", "b", nil, "d"]
let nonOptionals = optionals.flatMap{[=10=]}
print(nonOptionals)
打印:
[a, b, d]
注:
有 2 个 flatMap
函数:
一个flatMap
用于删除上面显示的非零值。参考 - https://developer.apple.com/documentation/swift/sequence/2907182-flatmap
另一个flatMap
用于连接结果。参考 - https://developer.apple.com/documentation/swift/sequence/2905332-flatmap
TL;DR
Swift 4
使用array.compactMap { [=21=] }
。 Apple 更新了框架,这样它就不会再导致 bugs/confusion.
Swift 3
为了避免潜在的 bugs/confusion,不要使用 array.flatMap { [=22=] }
来删除 nils;使用扩展方法,例如 array.removeNils()
(下面的实现,更新为 Swift 3.0)。
虽然 array.flatMap { [=22=] }
大部分时间都有效,但有几个理由支持 array.removeNils()
扩展:
removeNils
准确描述了您要执行的操作:删除 nil
值。不熟悉的人flatMap
需要查一查,查了再仔细看,就会得出和我下一点一样的结论;
flatMap
有两个不同的实现,它们做 两个完全不同的事情 。基于类型检查,编译器将决定调用哪一个。这在 Swift 中可能会有很大问题,因为类型推断被大量使用。 (例如,要确定变量的实际类型,您可能需要检查多个文件。)重构可能会导致您的应用程序调用错误版本的 flatMap
,这可能会导致 difficult-to-查找错误.
- 因为有两个完全不同的函数,所以理解
flatMap
就困难多了,因为你可以easily conflate the two.
flatMap
可以在非可选数组(例如 [Int]
)上调用,因此如果您将数组从 [Int?]
重构为 [Int]
,您可以 不小心留下 flatMap { [=36=] }
调用 ,编译器不会警告您。充其量只是 return 本身,最坏的情况是它会导致执行其他实现,从而可能导致错误。
- 在Swift 3中,如果不显式强制转换return类型,编译器会选择错误的版本,从而导致意想不到的后果. (参见下面的 Swift 3 部分)
- 最后,它减慢了编译器,因为类型检查系统需要确定调用哪个重载函数。
回顾一下,该函数有两个版本,不幸的是,它们都命名为 flatMap
。
通过移除嵌套层级来展平序列(例如 [[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: [=10=], repeatedValue: [=10=]) }
/// // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
///
/// let flatMapped = numbers.flatMap { Array(count: [=10=], repeatedValue: [=10=]) }
/// // [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]
}
从序列中删除元素(例如[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 是人们用来通过将 { [=40=] }
作为 transform
传递来删除 nils 的那个。这是有效的,因为该方法执行映射,然后过滤掉所有 nil
元素。
您可能想知道 "Why did Apple not rename #2 to removeNils()
"? 要记住的一件事是使用 flatMap
删除 nils 并不是 #2 的唯一用法.事实上,由于两个版本都采用 transform
函数,因此它们比上面的示例更强大。
例如,#1 可以轻松地将字符串数组拆分为单个字符(展平)并将每个字母大写(映射):
["abc", "d"].flatMap { [=12=].uppercaseString.characters } == ["A", "B", "C", "D"]
虽然数字 #2 可以轻松删除所有偶数(展平)并将每个数字乘以 -1
(地图):
[1, 2, 3, 4, 5, 6].flatMap { ([=13=] % 2 == 0) ? nil : -[=13=] } == [-1, -3, -5]
(请注意,最后一个示例可能会导致 Xcode 7.3 旋转很长时间,因为没有明确说明类型。进一步证明为什么这些方法应该有不同的名称。)
盲目使用 flatMap { [=36=] }
删除 nil
的真正危险不在于你在 [1, 2]
上调用它,而是在你在 [=50= 之类的东西上调用它时].在前一种情况下,它将无害地调用调用 #2 和 return [1, 2]
。在后一种情况下,您可能认为它会做同样的事情(无害 return [[1], [2]]
因为没有 nil
值),但它实际上会 return [1, 2]
因为它正在使用调用 #1.
flatMap { [=36=] }
用于删除 nil
的事实似乎更多是 Swift community 而不是来自 Apple。也许如果 Apple 注意到这种趋势,他们最终会提供 removeNils()
功能或类似的功能。
在那之前,我们只能提出自己的解决方案。
解决方案
// 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({ [=14=] }) {
result.append(element)
}
}
return result
}
}
(注意:不要与element.map
混淆...它与本文post中讨论的flatMap
无关。它使用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 .)
用法
let a: [Int?] = [1, nil, 3]
a.removeNils() == [1, 3]
例子
var myArray: [Int?] = [1, nil, 2]
assert(myArray.flatMap { [=16=] } == [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 { [=16=] } == [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 { [=16=] } == [[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 { [=16=] } == [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'
(注意最后一个,flatMap returns [1, 2, 3, 4]
而 removeNils() 应该是 return [[1], [2, 3], [4]]
。)
解决方案类似于链接到的answer@fabb。
不过,我做了一些修改:
- 我没有给方法命名
flatten
,因为序列类型已经有一个 flatten
方法,并且给完全不同的方法赋予相同的名称是我们陷入困境的原因第一名。更不用说误解 flatten
比 removeNils
更容易。
- 它没有在
OptionalType
上创建新类型 T
,而是使用 Optional
使用的相同名称 (Wrapped
)。
- 而不是 performing
map{}.filter{}.map{}
,这导致 O(M + N)
时间,我循环遍历数组一次。
- 我没有使用
flatMap
从 Generator.Element
到 Generator.Element.Wrapped?
,而是使用 map
。 map
函数中不需要 return nil
值,因此 map
就足够了。通过避免 flatMap
函数,很难将另一个(即第三个)具有相同名称但功能完全不同的方法混为一谈。
使用 removeNils
与 flatMap
的一个缺点是类型检查器可能需要更多提示:
[1, nil, 3].flatMap { [=17=] } // 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 { [=17=] }
a.removeNils()
我没怎么研究过,不过看来你可以补充一下:
extension SequenceType {
func removeNils() -> Self {
return self
}
}
如果您希望能够在包含非可选元素的数组上调用该方法。这可以使大规模重命名(例如 flatMap { [=36=] }
-> removeNils()
)更容易。
分配给自己与分配给新变量不同?!
看看下面的代码:
var a: [String?] = [nil, nil]
var b = a.flatMap{[=19=]}
b // == []
a = a.flatMap{[=19=]}
a // == [nil, nil]
令人惊讶的是,当您将 a = a.flatMap { [=87=] }
分配给 a
时,a = a.flatMap { [=87=] }
不会删除 nils,但是当您将其分配给 [=89] 时,它 会 删除 nils =]!我的猜测是,这与超载的 flatMap
和 Swift 选择了我们不想使用的那个有关。
您可以通过将其转换为预期类型来暂时解决问题:
a = a.flatMap { [=20=] } as [String]
a // == []
但这很容易忘记。相反,我建议使用上面的 removeNils()
方法。
更新
似乎有人提议弃用 flatMap
的 (3) 个重载中的至少一个:https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md
Swift 4
这适用于 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 { [=10=].optional }
}
}
测试:
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)
}
}
Swift 4
如果你有幸使用 Swift 4 那么你可以使用 compactMap
过滤掉 nil 值
array = array.compactMap { [=12=] }
例如
let array = [1, 2, nil, 4]
let nonNilArray = array.compactMap { [=10=] }
print(nonNilArray)
// [1, 2, 4]
Swift 5.3 及更高版本
的解决方案
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 { [=10=] }
}
}
我正在尝试编写 Array 的扩展,它将允许将可选 T 的数组转换为非可选 T 的数组。
例如这可以写成这样的自由函数:
func removeAllNils(array: [T?]) -> [T] {
return array
.filter({ [=11=] != nil }) // remove nils, still a [T?]
.map({ [=11=]! }) // convert each element from a T? to a T
}
但是,我无法将其作为扩展程序使用。我试图告诉编译器扩展仅适用于可选值数组。这是我目前所拥有的:
extension Array {
func filterNils<U, T: Optional<U>>() -> [U] {
return filter({ [=12=] != nil }).map({ [=12=]! })
}
}
(无法编译!)
无法限制为泛型结构或 class 定义的类型 - 数组设计用于任何类型,因此您不能添加适用于类型子集的方法。只能在声明泛型类型时指定类型约束
实现所需功能的唯一方法是创建全局函数或静态方法 - 在后一种情况下:
extension Array {
static func filterNils(_ array: [Element?]) -> [Element] {
return array.filter { [=10=] != nil }.map { [=10=]! }
}
}
var array:[Int?] = [1, nil, 2, 3, nil]
Array.filterNils(array)
或者简单地使用compactMap
(以前是flatMap
),它可以用来删除所有的nil值:
[1, 2, nil, 4].compactMap { [=11=] } // Returns [1, 2, 4]
从 Swift 2.0 开始,可以使用 where
子句添加适用于类型子集的方法。正如本 Apple Forum Thread 中所讨论的,这可用于过滤掉数组的 nil
值。感谢@nnnnnnnn 和@SteveMcQwark。
由于 where
子句尚不支持泛型(如 Optional<T>
),因此需要通过协议来解决。
protocol OptionalType {
typealias T
func intoOptional() -> T?
}
extension Optional : OptionalType {
func intoOptional() -> T? {
return self.flatMap {[=10=]}
}
}
extension SequenceType where Generator.Element: OptionalType {
func flatten() -> [Generator.Element.T] {
return self.map { [=10=].intoOptional() }
.filter { [=10=] != nil }
.map { [=10=]! }
}
}
let mixed: [AnyObject?] = [1, "", nil, 3, nil, 4]
let nonnils = mixed.flatten() // 1, "", 3, 4
从 Swift 2.0 开始,您不需要编写自己的扩展来过滤数组中的 nil 值,您可以使用 flatMap
,它会展平数组并过滤 nils:
let optionals : [String?] = ["a", "b", nil, "d"]
let nonOptionals = optionals.flatMap{[=10=]}
print(nonOptionals)
打印:
[a, b, d]
注:
有 2 个 flatMap
函数:
一个
flatMap
用于删除上面显示的非零值。参考 - https://developer.apple.com/documentation/swift/sequence/2907182-flatmap另一个
flatMap
用于连接结果。参考 - https://developer.apple.com/documentation/swift/sequence/2905332-flatmap
TL;DR
Swift 4
使用array.compactMap { [=21=] }
。 Apple 更新了框架,这样它就不会再导致 bugs/confusion.
Swift 3
为了避免潜在的 bugs/confusion,不要使用 array.flatMap { [=22=] }
来删除 nils;使用扩展方法,例如 array.removeNils()
(下面的实现,更新为 Swift 3.0)。
虽然 array.flatMap { [=22=] }
大部分时间都有效,但有几个理由支持 array.removeNils()
扩展:
removeNils
准确描述了您要执行的操作:删除nil
值。不熟悉的人flatMap
需要查一查,查了再仔细看,就会得出和我下一点一样的结论;flatMap
有两个不同的实现,它们做 两个完全不同的事情 。基于类型检查,编译器将决定调用哪一个。这在 Swift 中可能会有很大问题,因为类型推断被大量使用。 (例如,要确定变量的实际类型,您可能需要检查多个文件。)重构可能会导致您的应用程序调用错误版本的flatMap
,这可能会导致 difficult-to-查找错误.- 因为有两个完全不同的函数,所以理解
flatMap
就困难多了,因为你可以easily conflate the two. flatMap
可以在非可选数组(例如[Int]
)上调用,因此如果您将数组从[Int?]
重构为[Int]
,您可以 不小心留下flatMap { [=36=] }
调用 ,编译器不会警告您。充其量只是 return 本身,最坏的情况是它会导致执行其他实现,从而可能导致错误。- 在Swift 3中,如果不显式强制转换return类型,编译器会选择错误的版本,从而导致意想不到的后果. (参见下面的 Swift 3 部分)
- 最后,它减慢了编译器,因为类型检查系统需要确定调用哪个重载函数。
回顾一下,该函数有两个版本,不幸的是,它们都命名为 flatMap
。
通过移除嵌套层级来展平序列(例如
[[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: [=10=], repeatedValue: [=10=]) } /// // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]] /// /// let flatMapped = numbers.flatMap { Array(count: [=10=], repeatedValue: [=10=]) } /// // [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] }
从序列中删除元素(例如
[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 是人们用来通过将 { [=40=] }
作为 transform
传递来删除 nils 的那个。这是有效的,因为该方法执行映射,然后过滤掉所有 nil
元素。
您可能想知道 "Why did Apple not rename #2 to removeNils()
"? 要记住的一件事是使用 flatMap
删除 nils 并不是 #2 的唯一用法.事实上,由于两个版本都采用 transform
函数,因此它们比上面的示例更强大。
例如,#1 可以轻松地将字符串数组拆分为单个字符(展平)并将每个字母大写(映射):
["abc", "d"].flatMap { [=12=].uppercaseString.characters } == ["A", "B", "C", "D"]
虽然数字 #2 可以轻松删除所有偶数(展平)并将每个数字乘以 -1
(地图):
[1, 2, 3, 4, 5, 6].flatMap { ([=13=] % 2 == 0) ? nil : -[=13=] } == [-1, -3, -5]
(请注意,最后一个示例可能会导致 Xcode 7.3 旋转很长时间,因为没有明确说明类型。进一步证明为什么这些方法应该有不同的名称。)
盲目使用 flatMap { [=36=] }
删除 nil
的真正危险不在于你在 [1, 2]
上调用它,而是在你在 [=50= 之类的东西上调用它时].在前一种情况下,它将无害地调用调用 #2 和 return [1, 2]
。在后一种情况下,您可能认为它会做同样的事情(无害 return [[1], [2]]
因为没有 nil
值),但它实际上会 return [1, 2]
因为它正在使用调用 #1.
flatMap { [=36=] }
用于删除 nil
的事实似乎更多是 Swift community removeNils()
功能或类似的功能。
在那之前,我们只能提出自己的解决方案。
解决方案
// 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({ [=14=] }) {
result.append(element)
}
}
return result
}
}
(注意:不要与element.map
混淆...它与本文post中讨论的flatMap
无关。它使用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 .)
用法
let a: [Int?] = [1, nil, 3]
a.removeNils() == [1, 3]
例子
var myArray: [Int?] = [1, nil, 2]
assert(myArray.flatMap { [=16=] } == [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 { [=16=] } == [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 { [=16=] } == [[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 { [=16=] } == [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'
(注意最后一个,flatMap returns [1, 2, 3, 4]
而 removeNils() 应该是 return [[1], [2, 3], [4]]
。)
解决方案类似于链接到的answer@fabb。
不过,我做了一些修改:
- 我没有给方法命名
flatten
,因为序列类型已经有一个flatten
方法,并且给完全不同的方法赋予相同的名称是我们陷入困境的原因第一名。更不用说误解flatten
比removeNils
更容易。 - 它没有在
OptionalType
上创建新类型T
,而是使用Optional
使用的相同名称 (Wrapped
)。 - 而不是 performing
map{}.filter{}.map{}
,这导致O(M + N)
时间,我循环遍历数组一次。 - 我没有使用
flatMap
从Generator.Element
到Generator.Element.Wrapped?
,而是使用map
。map
函数中不需要 returnnil
值,因此map
就足够了。通过避免flatMap
函数,很难将另一个(即第三个)具有相同名称但功能完全不同的方法混为一谈。
使用 removeNils
与 flatMap
的一个缺点是类型检查器可能需要更多提示:
[1, nil, 3].flatMap { [=17=] } // 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 { [=17=] }
a.removeNils()
我没怎么研究过,不过看来你可以补充一下:
extension SequenceType {
func removeNils() -> Self {
return self
}
}
如果您希望能够在包含非可选元素的数组上调用该方法。这可以使大规模重命名(例如 flatMap { [=36=] }
-> removeNils()
)更容易。
分配给自己与分配给新变量不同?!
看看下面的代码:
var a: [String?] = [nil, nil]
var b = a.flatMap{[=19=]}
b // == []
a = a.flatMap{[=19=]}
a // == [nil, nil]
令人惊讶的是,当您将 a = a.flatMap { [=87=] }
分配给 a
时,a = a.flatMap { [=87=] }
不会删除 nils,但是当您将其分配给 [=89] 时,它 会 删除 nils =]!我的猜测是,这与超载的 flatMap
和 Swift 选择了我们不想使用的那个有关。
您可以通过将其转换为预期类型来暂时解决问题:
a = a.flatMap { [=20=] } as [String]
a // == []
但这很容易忘记。相反,我建议使用上面的 removeNils()
方法。
更新
似乎有人提议弃用 flatMap
的 (3) 个重载中的至少一个:https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md
Swift 4
这适用于 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 { [=10=].optional }
}
}
测试:
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)
}
}
Swift 4
如果你有幸使用 Swift 4 那么你可以使用 compactMap
array = array.compactMap { [=12=] }
例如
let array = [1, 2, nil, 4]
let nonNilArray = array.compactMap { [=10=] }
print(nonNilArray)
// [1, 2, 4]
Swift 5.3 及更高版本
的解决方案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 { [=10=] }
}
}