Is it possible to briefly evaluate a higher level function?

I am looking for a way to stop a higher level function after evaluating part of its input sequence.

Consider a situation where you are looking for the first index in a sequence that satisfies a certain condition. For example, suppose we are looking for the first position in an a of Int array, where the sum of two consecutive values ​​exceeds 100.

You can do this with a loop, for example:

 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 } 

The cycle stops as soon as the position of interest is detected.

We can rewrite this code with reduce as follows:

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

However, the drawback of this approach is that reduce goes all the way to a.count-2 , even if it finds a match in the very first index. The result will be the same, but it would be nice to reduce unnecessary work.

Is there a way to make reduce to stop trying to repeat matches, or perhaps another function that allows you to stop after finding the first match?

+5
source share
2 answers

As already mentioned, reduce specifically designed to evaluate the entire sequence and, therefore, is not intended for short circuiting. Using it this way to find the index of an element that matches a given predicate is best done with indexOf , as @Casey says .

Also, as in Swift 3, Sequence now has a first(where:) function that allows you to find the first element that satisfies a given predicate. This might be an even more suitable alternative than indexOf , as it returns an element instead of an index (although in your particular example they are the same).

You can write your example as follows:

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

However, if you need a more general high-level function that will iterate over a sequence and exit it, if it finds a non-zero result of a given predicate, you can always write your own find function:

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

Now you can write your firstAbove100 function as follows:

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

and now it will close when it finds a pair of elements that add above 100.

Or say, instead of returning the index of the first pair of elements in your array that add more than 100, now you want to return the sum of the elements. Now you can write it as follows:

 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) 

The find function iterates through the array, applying a predicate to each element (in this case, the indices of your array). If the predicate returns nil , it will continue to iterate. If the predicate returns non-nil, it will return this result and stop the iteration.

+4
source

indexOf will stop after it finds the first match so you can rewrite firstAbove100 like this:

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

All Articles