Swift函数找到匹配谓词的集合的第一个元素?

时间:2015-12-01 22:53:48

标签: swift collections

如果xs是一个集合而pred是一个返回Bool的闭包,是否有内置函数执行以下操作?

xs.filter(pred).first

这将获得与预测匹配的集合的第一个元素,如果没有匹配则为nil。对索引不感兴趣,但对元素本身不感兴趣。

2 个答案:

答案 0 :(得分:2)

不,没有,但你可以自己写一个:

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属性。

所以要真正让它变得懒惰并拥有所有@noescapethrowsSequenceType,你必须使用命令式方法:

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
    }
}

答案 1 :(得分:0)

在最简单的情况下,您想要的可能如下所示:

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),则可以通过相同的getFirstWithPredicate(_:)声明轻松获取与谓词匹配的最后一个元素。使用reverse()

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))