Swift: Reduce Function with a Closure

Swift: Reduce Function with a closure

Answer 2:

The first argument of the second (third, fourth) call to the closure is the result output by the previous call to the closure. The only exception is the first call, which has no previous call to inherit from, which is why reduce takes the value 0 as the second argument - it's a special value to feed into the first call.

Let's imagine the following scenario - you have an array of numbers:

let a = [1,2,3]

And you want to find the sum using reduce:

let sum = reduce(a,0) { (accumulator, nextValue) in accumulator + nextValue }

So let's see what happens on each call:

call#    acc    next    result
1 0 1 0+1 -> 1 # the zero is from reduce's second arg
2 1 2 1+2 -> 3 # the 1 is from the previous result
3 3 3 3+3 -> 6

Now we have run out of elements to process, so we return the final value 6, which is indeed the sum of 1,2 and 3


Let's imagine a slightly more complex scenario - adding a series of formatted number values to a string. Here's the reduce:

let sum = reduce(a,"") { (accumulator, nextValue) in accumulator + nextValue }

It looks almost identical, but you'll note that initial is set to "" instead of 0. The operator + now combines a String and an Int.

call#    acc    next    result
1 "" 1 ""+1 -> "1"
2 "1" 2 "1"+2 -> "12"
3 "12" 3 "12"+3 -> "123"

And we are done, and can print the string out, or whatever.


Now let's look at your first question!

Adding a series of points to a map is analogous to (but not quite the same as) adding numbers together; it's closer to the string example, where the accumulator has a different type (string/map) from the array elements (integer/treasure location).

You can certainly add "castle grey skull' to an empty map, and then add "shady cove" to a map with grey skull on it, and then add 'treasure location' to a map with "castle grey skull" and "shady cove" already drawn on it. Then you are done, and you can start the treasure hunt.

Your initial value is not zero, (or "") but the empty map.

When a new point (rect) is added, it's being added to the map that has the previous point on it, it's not being added to the previous point directly.

The previous point stays on the map, unchanged! it just has a new map marker nearby, to share stories with.

As to your question as to how a size zero rectangle can do anything, I assume that's just the same as a point.

How can a closure in Swift only be passed in a * for the reduce function?

static func * (lhs: Self, rhs: Self) -> Self is an operator function defined on the Numeric protocol. So, you are really just passing in a function that takes two arguments.

Reduce array of closures into single closure

Edit: My original answer misunderstood what your problem was. But seeing as my original answer might be useful to future readers, I'll leave it at the bottom.

Your compose function is nearly there! b(a) does not compile because MyClosure does not take another MyClosure. b(a) is invoking the closure ("function application"). not composition. Since compose returns a closure, why not return a closure? A typical closure looks like this in Swift:

{ (param) in return doSomethingTo(param) }

So let's return that!

return { (x) in return b(a(x)) }

This can be simplified to:

{ b(a($0)) } // "return" can be omitted as well!

This page (among other things) tells you how and when you can simplify closure syntaxes.


Original answer:

Using reduce is the correct choice here. The reduction operation is composition, so let's write a compose function first:

func compose<T>(_ x: @escaping (T) -> T, _ y: @escaping (T) -> T) -> (T) -> T {
{ y(x($0)) } // or { x(y($0)) } if you want it the other way
}

Then, we reduce. What's the identity? The identity is something that has these properties:

compose(identity, anything) == anything
compose(anything, identity) == anything

What function does that? The identity function!

So we get:

func reduceClosures<T>(_ closures: [(T) -> T]) -> (T) -> T {
closures.reduce({ $0 }, compose)
}

What does the reduce(_:_:) function do in Swift?

By the way, if you’re wondering what precisely reduce does, you can always refer to the source code, where you can see the actual code as well as a nice narrative description in the comments.

But the root of your question is that this code is not entirely obvious. I might suggest that if you’re finding it hard to reason about the code snippet, you can replace the opaque shorthand argument names, $0 and $1, with meaningful names, e.g.:

let verticalMax = pipsPerRowForRank.reduce(0) { previousMax, nextArray in 
max(nextArray.count, previousMax)
}

let horizontalMax = pipsPerRowForRank.reduce(0) { previousMax, nextArray in
max(nextArray.max() ?? 0, previousMax)
}

By using argument names that make the functional intent more clear, it often is easier to grok what the code is doing. IMHO, especially when there are multiple arguments, using explicit argument names can make it more clear.


That having been said, I’d probably not use reduce and instead do something like:

let verticalMax = pipsPerRowForRank
.lazy
.map { $0.count }
.max() ?? 0

To my eye, that makes the intent extremely clear, namely that we’re counting how many items are in each sub-array and returning the maximum count.

Likewise, for the horizontal one:

let horizontalMax = pipsPerRowForRank
.lazy
.flatMap { $0 }
.max() ?? 0

Again, I think that’s clear that we’re creating a flat array of the values, and then getting the maximum value.

And, in both cases, we’re using lazy to avoid building interim structures (in case our arrays were very large), but evaluating it as we go along. This improves memory characteristics of the routine and the resulting code is more efficient. Frankly, with an array of arrays this small, lazy isn’t needed, but I include it for your reference.


Bottom line, the goal with functional patterns is not to write code with the fewest keystrokes possible (as there are more concise renditions we could have written), but rather to write efficient code whose intent is as clear as possible with the least amount of cruft. But we should always be able to glance at the code and reason about it quickly. Sometimes if further optimization is needed, we’ll make a conscious decision to sacrifice readability for performance reasons, but that’s not needed here.

Swift: Reduce with closure

Swift's array class has a definition of reduce that most likely looks something like this:

func reduce<T>(initial: T, fn: (T, T) -> T) -> T {
var val = initial
for e in self {
val = fn(val, e)
}
return e
}

That is to say, the definition of reduce dictates the order in which parameters are passed to the closure you provide.


Note that the actual definition of Swift's reduce is more complicated than the one I provided above, but the example above is the basic gist.

Using the reduce() function in Swift without a second argument

What you are seeing is a trailing closure.

You write a trailing closure after the function call’s parentheses, even though the trailing closure is still an argument to the function. When you use the trailing closure syntax, you don’t write the argument label for the first closure as part of the function call.

(emphasis mine)

Starting from Swift 5.3, you can write multiple trailing closures for a function call. Before that only the last argument, that is also a closure, can be a trailing closure.

So the reduce call is equivalent to:

let totalIncome = peopleArray.reduce(0, {(result, next) -> Double in
return result + next.income
})

I am not sure whether this is a syntactical variation of writing the same thing (i.e. if somehow two arguments are indeed being passed into the reduce() function)

Yes, this is just a syntactical variation, or "syntactic sugar" as we'd like to call it.

What is the reduce() function doing, in Swift

Recall that reduce works by repeated applying a function (that is the closure you pass in) to an accumulator (that is the initial), for each element of the sequence:

var acc = initial
for element in array {
acc = f(acc, element) // f is the closure
}

Now knowing that, this reduction will make a lot more sense if you give meaningful names to $0.0, $0.1 and $1. Here I have renamed $0 to (prev, total), and $1 to curr.

func countUniques<T: Comparable>(_ array: Array<T>) -> Int {
let sorted = array.sorted()
let initial: (T?,Int) = (.none, 0)

let (_, total) = sorted.reduce(initial) { acc, curr in
let (prev, total) = acc
return (curr, prev == curr ? total : total + 1)
}
return total
}

prev is used to keep track of the previous element that we have seen. Notice how we always return curr as the first element of the pair. This is to say that the current element that we are seeing, becomes the "previous element" in the next iteration. Also note that it starts off .none (nil), as we have not seen anything yet.

If prev is not curr, we add 1 to total, otherwise we use the same total. In this way, we can count how many times we see a different element from the one just saw.

For a sorted array, "how many times we see a different element from the one just saw" is the same number as the number of unique elements, because the duplicates will all be grouped next to each other.

Is there a way to break from an array's reduce function in Swift?

As others have suggested, you can use contains for this purpose:

var flags = [false, false, true, false, false, true, false]
contains(flags, true) //--> true

Another option is to use find to search for the first instance of what you're looking for, in this case true:

var flags = [false, false, true, false, false, true, false]    
find(flags, true) // --> 2, returns nil if not found
let containsTrue = (find(flags, true) != nil)

Edit: Newer versions of Swift expose these functions on the related collection protocols instead of global functions.

var flags = [false, false, true, false, false, true, false]
flags.contains(where: { $0 == true })
flags.contains(true) // if checking for a specific element
let index = flags.firstIndex(where: ${ $0 == true }) // --> 2

Is there any overhead in wrapping a function in a closure to pass it as argument

There should not be any overhead if you compile with optimizations, as the compiler will most likely inline your closure.

You can verify this assumption with your first solution (as it supports both styles) by comparing the LLVM code Swift writes. LLVM is an intermediate representation used by the compiler right before creating actual machine code.

Write one file using the operator directly, i.e.:

let x = castAndCombine((1, 2), toType: Int.self, with: +)

Write a second file using the closure, i.e.:

let x = castAndCombine((1, 2), toType: Int.self, with: { (a: Int, b: Int) in a + b })

Now compile both with optimizations, asking Swift's compiler to produce the LLVM IR. Assuming your files are named main1.swift and main2.swift, you can run the following:

swift -O -emit-ir main1.swift 1>main1.ll
swift -O -emit-ir main2.swift 1>main2.ll

Both produced files should be identical.

diff main1.ll main2.ll
# No output

Note that the solutions suggested in the comments do not add any performance overhead either, as statically guaranteed casts do not cost any operation.



Related Topics



Leave a reply



Submit