是否可以缩短对更高级别功能的评估?

Is it possible to cut short evaluation of a higher-level function?

我正在寻找一种在评估部分输入序列后停止更高级别函数的方法。

考虑在满足特定条件的序列中查找第一个索引时的情况。例如,假设我们正在寻找 Int 数组 a 中的第一个位置,其中两个连续值的总和大于 100。

你可以用一个循环来做,像这样:

func firstAbove100(a:[Int]) -> Int? {
    if a.count < 2 {
        return nil
    }
    for i in 0..<a.count-1 {
        if a[i]+a[i+1] > 100 {
            return i
        }
    }
    return nil
}

一旦发现感兴趣的位置,循环就会停止。

我们可以使用 reduce 重写这段代码,如下所示:

func firstAbove100(a:[Int]) -> Int? {
    if a.count < 2 {
        return nil
    }
    return (0..<a.count-1).reduce(nil) { prev, i in
        prev ?? (a[i]+a[i+1] > 100 ? i : nil)
    }
}

但是,这种方法的缺点是 reduce 一直到 a.count-2,即使它在第一个索引处找到了匹配项。结果是一样的,但能减少不必要的工作就好了。

有没有办法让 reduce 停止尝试进一步的匹配,或者可能有一个不同的函数让你在找到第一个匹配后停止?

indexOf 将在找到第一个匹配项后停止,因此您可以将 firstAbove100 重写为如下内容:

func firstAbove100(a:[Int]) -> Int? {
    return a.count > 1 ? (a.startIndex..<a.endIndex-1).indexOf({ a[[=10=]] + a[[=10=] + 1] > 100 }) : nil
}

如前所述,reduce 是专门为评估整个序列而设计的,因此不是为了短路而设计的。以这种方式使用它来查找满足给定谓词的元素的索引最好使用 indexOf as .

另外,从 Swift 3 开始,Sequence 上现在有一个 first(where:) 函数,可以让您找到满足给定谓词的第一个元素。这可能是比 indexOf 更合适的替代方案,因为它 return 是元素而不是索引(尽管在您的特定示例中它们是相同的)。

你可以这样写你的例子:

func firstAbove100(_ a:[Int]) -> Int? {
    guard a.count > 1 else {return nil}

    return (0..<a.count-1).first { i in
        a[i]+a[i+1] > 100
    }
}

然而,如果你想要一个更通用的高级函数,它将遍历一个序列并在它找到给定谓词的非零结果时中断——你总是可以编写自己的 find 函数:

extension SequenceType {

    func find<T>(@noescape predicate: (Self.Generator.Element) throws -> T?) rethrows -> T? {
        for element in self {
            if let c = try predicate(element) {return c}
        }
        return nil
    }
}

您现在可以像这样编写 firstAbove100 函数:

func firstAbove100(a:[Int]) -> Int? {
    if a.count < 2 {
        return nil
    }
    return (0..<a.count-1).find { i in
        a[i]+a[i+1] > 100 ? i : nil
    }
}

现在,当它找到一对加起来大于 100 的元素时,它会短路。

或者假设您现在想要 return 元素的总和,而不是 return 数组中加起来大于 100 的第一对元素的索引。你现在可以这样写:

func sumOfFirstAbove100(a:[Int]) -> Int? {
    guard a.count > 1 else {return nil}
    return (0..<a.count-1).find { i in
        let sum = a[i]+a[i+1]
        return sum > 100 ? sum : nil
    }
}

let a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
print(sumOfFirstAbove100(a)) // prints: Optional(110)

find 函数将遍历数组,将谓词应用于每个元素(在本例中为数组的索引)。如果谓词returns nil,那么它将继续迭代。如果谓词 return 非零,那么它将 return 结果并停止迭代。