我正在寻找一种方法来在评估部分输入序列后停止更高级别的功能。
当您在满足特定条件的序列中查找第一个索引时,请考虑这种情况。例如,我们假设我们正在查找a
个数组Int
中的第一个位置,其中两个连续值的总和大于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
停止尝试进一步的匹配,或者可能是一个让你在找到第一场比赛后停止的不同功能?
答案 0 :(得分:4)
indexOf
会在找到第一个匹配后停止,因此您可能会将firstAbove100
重写为以下内容:
func firstAbove100(a:[Int]) -> Int? {
return a.count > 1 ? (a.startIndex..<a.endIndex-1).indexOf({ a[$0] + a[$0 + 1] > 100 }) : nil
}
答案 1 :(得分:4)
如前所述,reduce
是专门为评估整个序列而设计的,因此不会设计为短路。以这种方式使用它来查找满足给定谓词的元素的索引最好使用indexOf
作为@Casey says。
同样在Swift 3中,first(where:)
上有一个Sequence
函数,它允许您找到满足给定谓词的第一个元素。这可能是比indexOf
更合适的替代方法,因为它返回元素而不是索引(尽管在您的特定示例中它们是相同的)。
你可以像这样编写你的例子:
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以上的元素时,它现在会短路。
或者让我们说,而不是返回数组中添加到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
函数将遍历数组,将谓词应用于每个元素(在本例中为数组的索引)。如果谓词返回nil
,那么它将继续迭代。如果谓词返回非nil,那么它将返回该结果并停止迭代。