【问题标题】:Swift function to find first element of collection matching a predicate?Swift函数查找与谓词匹配的集合的第一个元素?
【发布时间】:2016-03-06 01:05:53
【问题描述】:

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

xs.filter(pred).first

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

【问题讨论】:

    标签: swift collections


    【解决方案1】:

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

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

    【讨论】:

    • return try filter(pred).first 使其编译。它不是最优的,因为 所有 序列元素都被评估了。出于某种原因,即使return try lazy.filter(pred).first 也无法解决这个问题。
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2012-01-21
      • 2020-04-29
      • 2014-07-04
      • 1970-01-01
      • 2014-06-22
      • 2013-10-30
      相关资源
      最近更新 更多