Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Reduce Function with a closure

Below is the code that I'm struggling to understand:

  let rectToDisplay = self.treasures.reduce(MKMapRectNull)  { //1
    (mapRect: MKMapRect, treasure: Treasure) -> MKMapRect in //2

     let treasurePointRect = MKMapRect(origin: treasure.location.mapPoint, size: MKMapSize(width: 0, height: 0)) //3

       return MKMapRectUnion(mapRect, treasurePointRect)
    }

My understanding of reduce function is:

var people [] // an array of objects
var ageSum = 0
ageSum = people.reduce(0) { $0 + $1.age}

//(0) = initial value
//$0 = running total
//$1 = an object in an array

My understanding of closure is:

{ (params) -> returnType in
  statements
}

My understanding for the code on top is:

//1 = initial value for the reduce function is set to (MKMapRectNull)

//2 = Instead of a running total and an object in an array, a closure is passed in with two arguments that returns a MKMapRect:

(mapRect: MKMapRect, treasure: Treasure) -> MKMapRect 

//3 = This is where I'm stuck. A struct, MKMapRect, is called with two parameters origin: treasure.location.mapPoint, and size: MKMapSize(width: 0, height: 0)

Question 1: How will MKMapSize calculate if the values passed in are 0,0 ? How is it getting the subsequent values and add them?

Question 2: When this line is returned to //2 closure return MKMapRectUnion(mapRect, treasurePointRect) how does it become the running total and how does it know to get to the next element of self.treasures ?

like image 399
user1107173 Avatar asked Dec 25 '22 19:12

user1107173


2 Answers

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.

like image 81
Alex Brown Avatar answered Jan 12 '23 01:01

Alex Brown


If it helps to think about reduce from an imperative perspective, this is essentially what your code is doing:

var rectToDisplay = MKMapRectNull
for treasure in self.treasures {
    let treasurePointRect = MKMapRect(origin: treasure.location.mapPoint, size: MKMapSize(width: 0, height: 0))
    rectToDisplay = MKMapRectUnion(rectToDisplay, treasurePointRect)
}
like image 37
Nate Cook Avatar answered Jan 12 '23 01:01

Nate Cook