【发布时间】:2016-03-06 01:05:53
【问题描述】:
如果xs 是一个集合,而pred 是一个返回Bool 的闭包,那么是否有一个内置函数可以执行以下操作?
xs.filter(pred).first
这将获取与预测匹配的集合的第一个元素,如果不匹配,则为 nil。对索引不感兴趣,对元素本身感兴趣。
【问题讨论】:
标签: swift collections
如果xs 是一个集合,而pred 是一个返回Bool 的闭包,那么是否有一个内置函数可以执行以下操作?
xs.filter(pred).first
这将获取与预测匹配的集合的第一个元素,如果不匹配,则为 nil。对索引不感兴趣,对元素本身感兴趣。
【问题讨论】:
标签: swift collections
没有,但你可以自己写一个:
extension SequenceType {
func first(@noescape pred: Generator.Element throws -> Bool) rethrows -> Generator.Element? {
return try filter(pred).first
}
}
编辑:这个版本不是最优的,因为filter 创建了一个全新的数组,即使只需要第一个元素。正如 Martin R 所指出的,lazy.filter 也不起作用。这对于使其与lazy 一起工作是必要的:
extension CollectionType {
func first(pred: Generator.Element -> Bool) -> Generator.Element? {
return lazy.filter(pred).first
}
}
因为:
@noescape 不能使用,因为 @noescape 意味着闭包不能转义当前函数,这在将它传递给惰性 filter 时是可能的(在被要求之前不评估元素 -> 必须转义谓词)throws 不能使用,因为过滤器是惰性的 -> 错误不会立即抛出,而是在第一次使用时,即调用 first 时,但 first 不能抛出因为它是一种财产。有一些关于在未来版本的 Swift 中使用 getter(和下标)的讨论。CollectionType,因为只有LazyCollectionType 具有first 属性。所以要真正让它变得懒惰并拥有所有 @noescape、throws 和 SequenceType,您必须使用命令式方法:
extension SequenceType {
func first(@noescape pred: Generator.Element throws -> Bool) rethrows -> Generator.Element? {
for elem in self where try pred(elem) {
return elem
}
return nil
}
}
【讨论】:
return try filter(pred).first 使其编译。它不是最优的,因为 所有 序列元素都被评估了。出于某种原因,即使return try lazy.filter(pred).first 也无法解决这个问题。
在最简单的情况下,您想要的可能如下所示:
let array = [18, 12, 35, 11, 12, 44]
var first: Int?
for element in array where element == 12 {
first = element
break
}
print(first) // prints: Optional(12)
如果确实需要设置谓词闭包,可以使用如下模式:
let array = [18, 12, 35, 11, 12, 44]
var first: Int?
let predicateClosure = { (value: Int) -> Bool in
return value == 12
}
for element in array where predicateClosure(element) {
first = element
break
}
print(first) // prints: Optional(12)
如果您需要重复这些操作,您可以使用SequenceType 协议扩展来重构您的代码:
extension SequenceType where Generator.Element == Int {
func getFirstWithPredicate(predicate: Int -> Bool) -> Int? {
for element in self where predicate(element) {
return element
}
return nil
}
}
let array = [18, 12, 35, 11, 12, 44]
let predicateClosure: Int -> Bool = {
return $0 == 12
}
let first = array.getFirstWithPredicate(predicateClosure)
print(first) // prints: Optional(12)
请注意,您不需要 predicate 闭包参数来转义您的 getFirstWithPredicate(_:) 方法,因此您可以在其前面添加 @noescape 属性(有关详细信息,请参阅 Nonescaping Closures):
extension SequenceType where Generator.Element == Int {
func getFirstWithPredicate(@noescape predicate: Int -> Bool) -> Int? {
for element in self where predicate(element) {
return element
}
return nil
}
}
let array = [18, 12, 35, 11, 12, 44]
let predicateClosure = { $0 == 12 }
let first = array.getFirstWithPredicate(predicateClosure)
print(first) // prints: Optional(12)
如果您希望前面的代码适用于任何类型的序列,您可以移除 Int 约束并重新声明您的 getFirstWithPredicate(_:) 方法,如下例所示:
extension SequenceType {
func getFirstWithPredicate(@noescape predicate: Generator.Element -> Bool) -> Generator.Element? {
for element in self where predicate(element) {
return element
}
return nil
}
}
let intArray = [18, 12, 35, 11, 12, 44]
let firstInt = intArray.getFirstWithPredicate { $0 == 12 }
print(firstInt) // prints: Optional(12)
let stringArray = ["Car", "Boat", "Plane", "Boat", "Bike"]
let firstString = stringArray.getFirstWithPredicate { $0 == "Boat" }
print(firstString) // prints: Optional("Boat")
如果您使用的是符合CollectionType 协议的对象(例如,Array),您可以使用reverse() 轻松获取与具有相同getFirstWithPredicate(_:) 声明的谓词匹配的最后一个元素:
extension SequenceType {
func getFirstWithPredicate(@noescape predicate: Generator.Element -> Bool) -> Generator.Element? {
for element in self where predicate(element) {
return element
}
return nil
}
}
struct Toy {
let name: String
let price: Int
}
let array = [
Toy(name: "Ball", price: 20),
Toy(name: "Car", price: 12),
Toy(name: "Plane", price: 35),
Toy(name: "Boat", price: 12),
]
let lastToyWithMatchingPrice = array.reverse().getFirstWithPredicate { $0.price == 12 }
print(lastToyWithMatchingPrice) // prints: Optional(Toy(name: "Boat", price: 12))
【讨论】: