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 thecontains
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
Swift - Writing a Byte Stream to File
How to Refresh Multiple Timers in Widget iOS14
Get the First Day of Week Without Weekcalendarunit
Hittest Prints Ar Entity Name Even When I am Not Tapping on It
Why I Can Not Use Equatable Function in My View When the View Can Use It in Swiftui
How to Reduce the Opacity of the Shadows in Realitykit
Error: Extraneous Argument Label 'No1:' in Call
Can Not Cast Value of Type Nstaggedpointerstring to Nsdictionary
Swift 5 Coredata Predicate Using Uuid
Code Highlighting in Latex for Swift
Swiftui Pass Two Child Views to View
How to Use Tabs to Evenly Space Out Description Strings in Swift
Tapping an Mkmapview in Swiftui
Dynamic Datasource/Delegates for Uitableview in Swift
Swift 1.2 Not Working with Same Function Name and Different Parameter