How to use flatMap() and reduce() on a range of a 2d array

Viewed 321

I'm having a 2d array and I want to use something similar to flatMap and reduce together with a range of each of the two dimension. In my example I want to add each number from position 1 to 3 in each row 0 to 2.

My code at the moment looks similar to the following and it does work.

let array = [
     [3,6,8,4],
     [3,7,4,6],
     [2,4,5,7],
     [5,3,7,4]
]

let row = 1
let col = 2
var reducedValue = 0        
        
(row-1...row+1).forEach {
     reducedValue = array[$0][col-1...col+1].compactMap { $0 }.reduce(reducedValue, +)
}

The expected output is:

51
//6 + 8 + 4 + 7 + 4 + 6 + 4 + 5 + 7

However I was wondering if there is a solution similar to the this one:

reducedValue = array[row-1...row+1][col-1...col+1].compactMap { $0 }.reduce(0, +)

Or maybe even without the compactMap similar to this:

reducedValue = array[row-1...row+1][col-1...col+1].reduce(0, +)
3 Answers

You could flatten the array of arrays and then reduce that. For example:

let total = array[row-1...row+1]
    .flatMap { $0[col-1...col+1] }
    .reduce(0, +)

That yields:

51


If you wanted to avoid the overhead of building that flattened array (which is immaterial unless dealing with very large subarrays, i.e., millions of values), you can perform the calculation lazily:

let total = array[row-1...row+1]
    .lazy
    .flatMap { $0[col-1...col+1] }
    .reduce(0, +)

Another option, which adds the values of the selected rows and columns without creating an intermediate array:

let value = array[row-1...row+1].reduce(0, { $0 + $1[col-1...col+1].reduce(0, +) })
print(value) // 51

The outer reduce() iterates over the selected rows and adds for each row the sum of the values of the values in the selected columns, which are computed in the inner reduce().

While the functional approach would give you the most succinct code

let result = array[row-1...row+1].reduce(into: 0) { $0 += $1[col-1...col+1].reduce(into: 0, +=) }

, the classic imperative code should not be ignored:

var result = 0
for i in row-1...row+1 {
    for j in col-1...col+1 {
        result += array[i][j]
    }
}

The imperative approach, though more verbose and not 100% Swift idiomatic (uses a var when a let alternative exists), is more readable in this particular context, of processing mathematical concepts.

Related