Does Swift Have Short-Circuiting Higher-Order Functions Like Any or All

Does Swift have short-circuiting higher-order functions like Any or All?

Sequence (and in particular Collection and Array) has a (short-circuiting) contains(where:) method taking a boolean predicate as argument. For example,

if array.contains(where: { $0 % 2 == 0 })

checks if the array contains any even number.

There is no "all" method, but you can use contains() as well
by negating both the predicate and the result. For example,

if !array.contains(where: { $0 % 2 != 0 })

checks if all numbers in the array are even. Of course you can define a custom extension method:

extension Sequence {
func allSatisfy(_ predicate: (Iterator.Element) -> Bool) -> Bool {
return !contains(where: { !predicate($0) } )
}
}

If you want to allow "throwing" predicates in the same way as the
contains method then it would be defined as

extension Sequence {
func allSatisfy(_ predicate: (Iterator.Element) throws -> Bool) rethrows -> Bool {
return try !contains(where: { try !predicate($0) } )
}
}

Update: As James Shapiro correctly noticed, an allSatisfy method has been added to the Sequence type in Swift 4.2 (currently in beta), see

  • SE-0027 Add an allSatisfy algorithm to Sequence

(Requires a recent 4.2 developer snapshot.)

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

As already said, reduce is specifically designed in order to evaluate an entire sequence and therefore not designed to short-circuit. Using it in this way to find an the index of an element that meets a given predicate is best done with indexOf as @Casey says.

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

You could write your example like this:

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 want a more general high level function that will iterate through a sequence and break out if it finds a non-nil result of a given predicate – you could 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
}
}

You could now write your firstAbove100 function like this:

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 it will now short-circuit when it finds a pair of elements that add to above 100.

Or let's say instead of returning the index of the first pair of elements in your array that add to greater than 100, you now want to return the sum of the elements. You could now write it like this:

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 will iterate through the array, applying the predicate to each element (in this case the indices of your array). If the predicate returns nil, then it will carry on iterating. If the predicate returns non-nil, then it will return that result and stop iterating.

How to use Swift's higher order functions for parsing dynamic dictionary data in swift?

Can we convert your algorithm to a functional style? Yes. Is it a good idea? Probably not in this case. But here's how.

You didn't give any type information, so I'll use this type:

let outerArrayHolder: [[String: Any]] = [
[
"dictDynamicKey": ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]

And you want to find the key corresponding to the array that contains inputValue:

let inputValue = "dynamicValue2"

The functional strategy is to map each dictionary in outerArrayHolder to its first key that has a matching value. If a dictionary has no such key, the dictionary is mapped to nil. Then we throw away the nils and take the first remaining value.

We can do it with filter, as requested:

let key = outerArrayHolder.lazy
.compactMap {
$0.lazy
.filter { ($0.value as? [String])?.contains(inputValue) ?? false }
.map { $0.key }
.first }
.first

But we can save a lazy and a first using first(where:):

let key = outerArrayHolder.lazy
.compactMap({
$0
.first(where: { ($0.value as? [String])?.contains(inputValue) ?? false })
.map { $0.key }
}).first

whats the performance of chaining swift's array higher order functions?

The compiler is not smart enough to understand the functions in the standard library and come with ways to optimize their calls.

However, the problem with chaining is not the double iteration. From performance perspective iteration is not a big problem. The real problem are memory implications. Every time you call .filter or .map, the result is a new sequence, therefore we need memory to store the temporary sequence.

To alliviate this, Swift arrays have .lazy property, which enables you to chain lazily, without creating intermediate results and without multiple iterations:

bookResults.books
.lazy
.filter { !$0.pictures.isEmpty }
.map { ... }

As another solution, you can always merge filter and map using compactMap:

let presentableBooks = bookResults.books
.compactMap { book in
guard let image = book.pictures.first else { return nil }
return SearchBooks.Search.Response.BookPresentable(
pictureKey: image,
pictureStatus: .downloading,
name: book.name,
price: book.price
)
}

Swift short-circuiting with logical operators not working as expected

Short circuiting means that the next part of the expression is not evaluated only if the result is already clear. If the part before && is true then the result can still be both false and true and the next part has to be evaluated.

The cases are:

1. true && true => true
2. true && false => false
3. false && false => false
4. false && true => false

And after evaluating the left operand we have:

true && ??

which can end either in case 1 or 2, which have different results.

On the other hand, if we had:

false && ??

Then the result would be either case 3 or 4, which are both false and the expression will short-circuit.

Higher order function on a nested array

You can try this -

let data: [[String:Double]] = [["Bost" : 80], ["Craig" : 70], ["Dans" : 50]]
var total: Double = 0
for packet in data {
for (key, value) in packet {
total += value
}
}

print(total)

UPDATE

Using reduce

let data: [[String:Double]] = [["Bost" : 80], ["Craig" : 70], ["Dans" : 50]]
var total: Double = 0
for packet in data {
total += packet.values.reduce(0, +)
}
print(total)


Related Topics



Leave a reply



Submit