1

I have an array of values [CGFloat] and array of days [CGFloat] with which each value is associated (the time is also important so the days array is a decimal value).

Each day has between 0 and n values. (n typically less than 5 or 6)

I want to find the mean value for each day, so after the calculations I would like to have an array of means [CGFloat] and an array of days [CGFloat], or a dictionary of the two combined or an array of [CGPoint]. I am fairly certain this can be done with either a mapping or reducing or filter function, but I'm having trouble doing so. For instance

the third day might look like [2.45, 2.75, 2.9] with associated values [145.0, 150.0, 160.0]

And I would like to end with day[2] = 2.7 and value[2] = 151.7

or

[CGPoint(2.7, 151.7)] or [2.7 : 151.7]

Can anyone offer guidance?

Some code:

let xValues : [CGFloat] = dates.map{round((CGFloat($0.timeIntervalSinceDate(dateZero))/(60*60*24))*100)/100}
let yValues : [CGFloat] = valueDoubles.map{CGFloat($0)}
//xValues and yValues are the same length
var dailyMeans = [CGFloat]()
var xVals = [CGFloat]()

let index = Int(ceil(xValues.last!))

for i in 0..<index{

    let thisDay = xValues.enumerate().filter{$0.element >= CGFloat(i) && $0.element < CGFloat(i+1)}
    if thisDay.count > 0{
        var sum : CGFloat = 0
        var day : CGFloat = 0
        for i in thisDay{
            sum += yValues[i.index]
            day += xValues[i.index]
        }
        dailyMeans.append(sum/CGFloat(thisDay.count))
        xVals.append(day/CGFloat(thisDay.count))
    }

}

The above code works, but also has to do that enumerate.filter function values.count * days.last times. So for 40 days and 160 readings.. like 6500 times. And I'm already using way too much processing power as it is. Is there a better way to do this?

edit: forgot a line of code defining index as the ceiling of xValues.last

This has been seen 1000 times so I thought I would update with my final solution:

var daySets = [Int: [CGPoint]]()
// points is the full array of (x: dayTimeInDecimal, y: value)
for i in points {
    let day = Int(i.x)

    daySets[day] = (daySets[day] ?? []) + [i]
}

let meanPointsEachDay = daySets.map{ (key, value) -> CGPoint in
    let count = CGFloat(value.count)
    let sumPoint = value.reduce(CGPoint.zero, {CGPoint(x: $0.x + $1.x, y: $0.y + $1.y)})
    return CGPoint(x: sumPoint.x/count, y: sumPoint.y/count)
}
5
  • 1
    any reason for using CGFloat and CGPoint here? Commented Jan 17, 2016 at 19:08
  • It will probably be quite difficult to improve the performance while still using raw data types, like arbitrary values in random arrays that are somehow related. I would suggest creating a class or struct which contains a date and a value (or even only use a tuple). But splitting the actual data across two arrays is not going to be fun to deal with. Commented Jan 17, 2016 at 19:20
  • The reason for CGFloat/CGPoint is that I'm drawing these with core graphics into a chart. Commented Jan 17, 2016 at 20:44
  • And yes I thought about using a tuple or a struct for all the data points. It turns out that cleaning up my code a bit drastically improved performance and now I'm hardly making a blip on cpu usage. I was doing some ridiculously repetitive calculations that I ditched and now just use an index of points for each separate data set, and draw them using that index and just pulling from the same array of CGPoints. Seems this code is sufficient for my purposes though. Thanks again from replying. Commented Jan 17, 2016 at 20:48
  • Happy to help while not strictly answering question - but glad the performance improved. Commented Jan 17, 2016 at 20:49

1 Answer 1

3
// must be sorted by 'day'!!!
let arrA0 = [2.45, 2.75, 2.9, 3.1, 3.2, 3.3]
// associated values
let arrA1 = [145.0, 150.0, 160.0, 245.0, 250.0, 260.0]

let arr = Array(zip(arrA0, arrA1))

// now i have an array of tuples, where tuple.0 is key and tuple.1 etc. is associated value
// you can expand the tuple for as much associated values, as you want
print(arr)
// [(2.45, 145.0), (2.75, 150.0), (2.9, 160.0), (3.1, 245.0), (3.2, 250.0), (3.3, 260.0)]




// now i can perform my 'calculations' the most effective way
var res:[Int:(Double,Double)] = [:]
// sorted set of Int 'day' values
let set = Set(arr.map {Int($0.0)}).sort()
// for two int values the sort is redundant, but
// don't be depend on that!

print(set)
// [2, 3]
var g = 0
var j = 0
set.forEach { (i) -> () in
    var sum1 = 0.0
    var sum2 = 0.0
    var t = true
    while t && g < arr.count {
        let v1 = arr[g].0
        let v2 = arr[g].1
        t = i == Int(v1)
        if t {
            g++
            j++
        } else {
            break
        }
        sum1 += v1
        sum2 += v2
    }
    res[i] = (sum1 / Double(j), sum2 / Double(j))
    j = 0
}
print(res)
// [2: (2.7, 151.666666666667), 3: (3.2, 251.666666666667)]

see, that every element of your data is process only once in the 'calculation', independent from size of 'key' set size

use Swift's Double instead of CGFloat! this increase the speed too :-)

and finally what you are looking for

if let (day, value) = res[2] {
    print(day, value) // 2.7 151.666666666667
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.