Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Averaging elements in array of arrays by index using functional programming

I have an array of arrays of Doubles. For example:

let mceGain = [[3,4,5],[7,4,3],[12,10,7]] // Written as integers for simplicity here

I would now like to average the elements in the different arrays with corresponding indexes. So I would have an output looking somewhat like this:

//firstAvg: (3+7+12)/3 = 7.33
//secondAvg: (4+4+10)/3 = 6
//thirdAvg: (5+3+7)/3 = 5

Then finally I would like to store these averages in a simpler array:

//mceGain: [7.33,6,5]

I have tried to do this with a double for-loop with a switch-statement inside, but this seems to be unnecessarily complicated. I assume the same result could be achieved using a combination of reduce(), map() and filter(), but I cannot seem to wrap my head around it.

like image 507
matiastofteby Avatar asked Mar 19 '26 16:03

matiastofteby


2 Answers

Let's analyse what you want to do here. You start with an array of arrays:

[[3,4,5],[7,4,3],[12,10,7]]

and you want to transform each subarray into a number:

[7,6,5]

Whenever you have this kind of "transform each element of this sequence into something else" situation, use map.

When you compute the average, you need to transform a sequence of things into just one thing. This means that we need reduce.

let array: [[Double]] = [[3,4,5],[7,4,3],[12,10,7]]
let result = array.map { $0.reduce(0.0, { $0 + $1 }) / Double($0.count) }

With comments:

let array: [[Double]] = [[3,4,5],[7,4,3],[12,10,7]]
let result = array.map { // transform each element like this:
    $0.reduce(0.0, { $0 + $1 }) // sums everything in the sub array up 
    / Double($0.count) } // divide by count

EDIT:

What you need to do is to "transpose" the array first, then do the map and reduce:

array[0].indices.map{ index in // these three lines makes the array [[3, 7, 12], [4, 4, 10], [5, 3, 7]]
    array.map{ $0[index] }
}
.map { $0.reduce(0.0, { $0 + $1 }) / Double($0.count) }
like image 146
Sweeper Avatar answered Mar 22 '26 14:03

Sweeper


This should answer your comment below

let elms: [[Double]] = [[3, 5, 3], [4, 4, 10] , [5, 3, 7]]

func averageByIndex(elms:[[Double]]) -> [Double]? {
    guard let length = elms.first?.count else { return []}

    // check all the elements have the same length, otherwise returns nil
    guard !elms.contains(where:{ $0.count != length }) else { return nil }

    return (0..<length).map { index in
        let sum = elms.map { $0[index] }.reduce(0, +)
        return sum / Double(elms.count)
    }
}

if let averages = averageByIndex(elms: elms) {
    print(averages) // [4.0, 4.0, 6.666666666666667]
}
like image 33
Luca Angeletti Avatar answered Mar 22 '26 15:03

Luca Angeletti



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!