Swift: Array map or reduce, enumerate with index - arrays

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)
}

// 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
}

Related

find max among 3 (user populated) arrays (which may be empty)

I am populating 3 arrays and I've no idea which one is being populated (depends on user). My goal is 2 fold.
find which array is being populated
find the Max value Among the 3 arrays (or the arrays which are populated)
eg:
hrGenY = [100,101,102]
cadGenY = [80, 81, 82]
powerGenY = [100,104,106]
This is what I'm doing right now. (This code here is just to determine which array is being populated and NOT nil)
var whichTelemery: [Int] = []
if !powerGenY.isEmpty {
whichTelemery = powerGenY
} else if !hrGenY.isEmpty {
whichTelemery = hrGenY
} else if !cadGenY.isEmpty {
whichTelemery = cadGenY
}
after which I utilise this to determine the X & Y scale for my chart. I use whichever is the Max to normalise the scale.
var yMax: CGFloat = 0.0
var xMax: CGFloat = 0.0
var yMaxTemp: [CGFloat] = [0,0]
var xMaxTemp: [CGFloat] = [0,0]
// Determine yMax & xMax for Scale Resolution
if !dataPointsY.isEmpty {
xMaxTemp[0] = dataPointsX.max()!
yMaxTemp[0] = CGFloat((dataPointsY.max()! >= ftpPlus) ? (1.2*dataPointsY.max()!) : ftpPlus)
xMax = xMaxTemp.max()!
yMax = yMaxTemp.max()!
}
if !whichTelemery.isEmpty && dataPointsY.isEmpty {
xMaxTemp[1] = CGFloat(whichTelemery.count < 900 ? 900 : whichTelemery.count + 1)
yMaxTemp[1] = CGFloat((CGFloat(whichTelemery.max()!) >= ftpPlus) ? (1.2*CGFloat(whichTelemery.max()!)) : ftpPlus)
xMax = xMaxTemp.max()!
yMax = yMaxTemp.max()!
}
The problem with this method is I am making the assumption that the user's power (if populated) will always be higher than the HR(heart rate) which may not be true. When this happens, the HR line will not get drawn as it's above the yMax limit.
The way I'm thinking of doing it is very crude which would involve yet another group of checks to see which of the 3 arrays has the highest number. Would appreciate some better more elegant way.
Thanks
What about getting the max value for your normalisation by doing
let max = [hrGenY.max() ?? 0.0, cadGenY.max() ?? 0.0, powerGenY.max() ?? 0.0].max()

Kotlin: initialize 2D array

I am in a loop, reading 2 columns from a file. I read R, T combinations, 50 times. I want R and T to be in an array so I can look up the Nth pair of R, T later in a function. How do I put the R, T pairs in an array and look up the, say, 25th entry later in a function?
For example:
for (nsection in 1 until NS+1) {
val list: List<String> = lines[nsection + 1].trim().split("\\s+".toRegex())
val radius = list[0].toFloat()
println("Radius = $radius")
val twist = list[8].toFloat()
println("twist = $twist")
}
Would like to pull radius and twist pairs from a table in a function later. NS goes up to 50 so far.
You can use map() on your range iterator to produce a List of what you want.
val radiusTwistPairs: List<Pair<Float, Float>> = (1..NS).map { nsection ->
val list = lines[nsection + 1].trim().split("\\s+".toRegex())
val radius = list[0].toFloat()
println("Radius = $radius")
val twist = list[8].toFloat()
println("twist = $twist")
radius to twist
}
Or use an Array constructor:
val radiusTwistPairs: Array<Pair<Float, Float>> = Array(NS) { i ->
val list = lines[i + 2].trim().split("\\s+".toRegex())
val radius = list[0].toFloat()
println("Radius = $radius")
val twist = list[8].toFloat()
println("twist = $twist")
radius to twist
}

2D array lookup in swift - similar to vlookup in excel

Re:
Finding a value in an array of arrays (similar to VLOOKUP function in Excel) in Swift
The above shows a method for determining the next lowest value in a 2D array given a search value. Reproduced here for convenience:
let testArray: [[Double]] = [
[0,0],
[1000,20.5],
[3000,21],
[3500,22.5],
[3300,21],
]
let income: Double = 3500
var closest = testArray[0][0]
var closestDif = closest - income
for innerArray in testArray {
let value = innerArray[0]
let thisDif = value - income
guard thisDif <= 0 else {
continue
}
if closestDif < thisDif {
closestDif = thisDif
closest = value
guard closestDif != 0 else {
break
}
}
}
print(closest)
The value returned for closest is 3500. Can someone please describe how we then retrieve the corresponding second number in the array [3500, 22.5] i.e. 22.5?
(edit)
Is enumerated(){....} a cleaner way to do this?
Thanks!
You can easily modify Martin R's answer from the linked Q&A to keep the whole inner array in compactMap and then find the maximum based on the first element of each inner array.
let result = testArray.compactMap { $0[0] <= income ? $0 : nil }.max(by: {$0.first! < $1.first!})! // [3500, 22.5]
#David Pasztor thanks your solution works nicely. I’m working swift 3 so I had to substitute “flatMap” for "compactMap" but otherwise it works great and just one line of code! I have used the same technique to also obtain the nearest higher values in the data to the search value (income) and then interpolate to get a value in the second column proportional to the search value income. The interpolation requires guarding against divide by zero when the search value income equals one of the values in the first column in which case the corresponding result0[0],[1] and result1[0],[1] are identical.
let testarray:[[Double]] = [
[0,0],
[1000,20.5],
[3000,21],
[3500,22.5],
[3300,21],
]
let income:Double = 3400
let result0 = testarray.flatMap { $0[0] <= income ? $0 : nil }.max(by: {$0.first! < $1.first!})!
let result1 = testarray.flatMap { $0[0] >= income ? $0 : nil }.min(by: {$0.first! < $1.first!})!
if income - result0[0] < 0.001 {
let interp = result0[1]
print(interp)
}
else {
let interp = result0[1] + (result1[1] - result0[1])*(income - result0[0])/(result1[0] - result0[0])
print(interp) // 21.75
}

Merging arrays of dictionaries based on dates

I have 2 arrays, both of kind [[String:Any]] , where each element :
["date":Date,"value":CGFloat] //always looks like this
I might even have more than 2 (!)
I would like to create a single array with the same structure that sums all of them (2 or more) for each date that appears in all of them.
If the date of array1 does not appear on the others(array2, etc) I will simply add 0 to the value at array 1 for this specific date.
Is there a simple efficient way to do so ?
Instead of dictionaries use structs, it's more convenient:
struct MyStruct {
let date: Date
let value: CGFloat
}
Let's create 3 arrays of MyStructs:
let now = Date()
let later = now.addingTimeInterval(3600)
let earlier = now.addingTimeInterval(-3600)
let array1: [MyStruct] = [MyStruct(date: now, value: 1),
MyStruct(date: later, value: 2)]
let array2: [MyStruct] = [MyStruct(date: now, value: 3),
MyStruct(date: later, value: 4)]
let array3: [MyStruct] = [ MyStruct(date: earlier, value: 5),
MyStruct(date: later, value: 6)]
Now, let's group the elements and add the values for the elements with the same date property:
let allArrays = array1 + array2 + array3
let dict = Dictionary(allArrays.map { ($0.date, $0.value) },
uniquingKeysWith: { $0 + $1 })
All you have to do now is convert it back to an array of MyStruct:
let newArray = dict.map { MyStruct(date: $0.key, value: $0.value) }
And you can check the results like so:
for element in newArray {
print("date =", element.date, "value =", element.value)
}
I found a way, assuming data is inside a structure(not a dic) which is a better practice.
I will put all arrays into a single large array, sort it by dates, loop on it and as long as date is equal previous date(or close enough to equality), I will sum the values up. When the next date is different, I will save the date and the sum.
//create a combined array from all given arrays
var combined = [RootData]()
for list in dataSets {combined.append(contentsOf: list)}
//sort it by dates
let sortedArray = combined.sorted { $0.date < $1.date }
//new array - sum of all
var sumData = [RootData]()
var last:Date = sortedArray[0].date //set starting point
var sum:CGFloat = 0
for element in sortedArray
{
//same date - accumulate(same is less than 1 sec difference)
if(abs(element.date.seconds(from: last)) <= 1) {
sum+=element.value
}
//save
else {
sumData.append(RootData(value:sum,date:last))
sum=element.value
}
last=element.date
}
//last object
sumData.append(RootData(value:sum,date:last))
return averageData
Here RootData is a simple structure for the data with :
value:CGFloat
date:Date
Works as expected.
Because dates are not always completely equal , I check equality by assuming 1 second or less is the same date.

Iterating through array to find occurrences of value

I have been trying to iterate through an array to check how many times two particular values occur. I came up with this code:
var paymentNum = 0
var creditNum = 0
for index in images {
if images.objectAtIndex(index) == 0 {
paymentNum = paymentNum + 1
} else {
creditNum = creditNum + 1
}
}
This doesn't seem to work though. I just get the error 'AnyObject' is not convertible to 'Int'.
Where am I going wrong?
I'm making some pretty huge assumptions because you don't have much detail about what's going on.
I'm assuming that images is an NSArray. NSArray in Objective-C translates into an [AnyObject] in swift.
When you have a for-each style loop like this
for value in array
{}
the loop will iterate through each value in the array. Value is the actual object in the array, not a counter as in a for(int i = 0; i < 10; i++) style loop
So what you're probably really expecting to do is
for item in images {
if item == 0 {
paymentNum++
} else {
creditNum++
}
}
you also might need to cast the loop from an [AnyObject] to an [Int] like this
let tmp = images as [Int]
for item in tmp
{...}
or, cast the value pulled out of the array like this
for item in images {
if (item as Int) == 0 {
paymentNum++
} else {
creditNum++
}
}
But the former is probably preferable
paste ina playground:
import Foundation
let images = [1, 0, 1, 1, 0, 0, 1, 0, 1, 1]
var paymentNum = 0
var creditNum = 0
for item in images {
println(item)
if item == 0 {
paymentNum = paymentNum + 1
} else {
creditNum = creditNum + 1
}
}
println(paymentNum) //4
println(creditNum) //6
There's a more compact way of achieving that, in just 2 lines of code.
It makes the assumption that an array element it's a credit if its value is 1, otherwise is 0.
That said, if you sum up all elements of array, you obtain the number of credits - the number of payments is the array size minus the number of credits.
To sum all elements of the array, the simplest way is using the reduce method (more info here)
var creditNum = images.reduce(0, combine: { $0 + $1 } )
var paymentNum = images.count - creditNum
If using values different than 0 or 1 to identify credits and payments, then the calculation can be modified to check for explicit values as follows:
var creditNum = images.reduce(0, combine: { $0 + ($1 == 1 ? 1 : 0) } )
Here it takes the previous count ($0) and add either 1 or 0, depending whether the element array ($1) is equal to 1 or not.

Resources