swift 从个别范围记录取得汇总统计数据

4ioopgfo  于 2022-10-31  发布在  Swift
关注(0)|答案(1)|浏览(190)

我正在尝试获取各个领域记录的聚合数据,以便将其绘制在图表上。
我有一个“WorkDay”领域对象,其中存储了“开始时间”、“结束时间”、“单一时间收入”、“惩罚性收入”、“总收入”等值。
一个图表示例是,我想收集一整年所有工作日的“总收入”,并将其与前几年所有工作日的“总收入”绘制成图表。
我目前的方法是使用"for循环“循环给定年份的所有WorkDay领域对象,并将WorkDay中的值存储在自定义数据结构中。我将领域WorkDay对象中的值移动到数据结构中的数组中。我之所以要将它们添加到数组中,是因为这样可以计算该年份的”averageTotalIncome“以及”totalIncome“。在数组上运行“reduce(0,+)”,可以为整个一年创建一个数组。
自定义数据结构看起来类似于:

import Foundation
import SwiftDate

struct TimePeriodData {

    //MARK: - Time Period

    var timePeriod: TimePeriod = .week
    var date: Date = Date()
    var localDate: Date = Date()

    //MARK: - Work Day Times

    var startTimeArray: [Date] = []
    var endTimeArray: [Date] = []

    //MARK: - Work Day Hours

    var hoursSingleTimeArray: [Double] = []
    var hoursPenaltyArray: [Double] = []
    var hoursTotalArray: [Double] = []

    //MARK: - Work Day Income

    var singleTimeIncomeArray: [Double] = []
    var penaltyIncomeArray: [Double] = []
    var totalIncomeArray: [Double] = []
}

然后,我从领域收集所有工作日,函数如下所示:

private func updateWorkDays() {
    // Get Time Period Dates
    let start = timePeriodStart()
    let end = timePeriodEnd()

    // Get Current User Work Days for Time Period
    let allUserWorkDays = realm.objects(WorkDay.self).where { workDay in
        workDay.date.contains(start ... end)
    }
    workDays = allUserWorkDays
}

一旦我有了工作日,我就将数据移动到“TimePeriodData”中,SwiftUI将使用它来生成图表等。代码如下所示:

private func getData(for inputDate: Date, from inputWorkDays: Results<WorkDay>) -> TimePeriodData {
    var data = TimePeriodData()
    for workDay in inputWorkDays {
        // Time Period
        data.timePeriod = self.timePeriod
        data.date = inputDate
        data.localDate = workDayListBrain.getLocalDate(from: inputDate)

        // Work Day Times
        data.startTimeArray.append(workDay.startTime)
        data.endTimeArray.append(workDay.endTime)

        // Work Day Hours
        data.hoursSingleTimeArray.append(workDay.hoursSingleTime)
        data.hoursPenaltyArray.append(workDay.hoursPenalty)
        data.hoursTotalArray.append(workDay.hoursTotal)

        // Work Day Income
        data.singleTimeIncomeArray.append(workDay.singleTimeIncome)
        data.penaltyIncomeArray.append(workDay.penaltyIncome)

    }
    return data
}

当“getData”返回时,UI将更新。但是,我发现当我对一整年使用“getData”时,我需要循环100个工作日对象,这需要大约2秒。我知道我还需要为前几年创建“TimePeriodData”,以便根据其他数据绘制图形。假设他们有100个工作日,每个都需要2秒。这加起来很快。
我看过其他帖子,人们循环50,000个领域对象,只需要0.1秒。考虑到我只循环100个领域对象,只需要2秒,我在这里做了什么根本性的错误吗?有没有更好的方法来处理这个问题?
到目前为止,我尝试使用map函数而不是'for循环',看起来有点像这样:

data.startTimeArray = inputWorkDays.compactMap { workDay in workDay.startTime } as [Date]
    data.endTimeArray = inputWorkDays.compactMap { workDay in workDay.endTime } as [Date]

但是“compactMap”在速度上并没有明显的提高。我也试着在BG线程上完成这一切,这让事情“感觉”更快,因为UI在处理数据时从来没有停滞过,但它仍然需要2秒钟来更新。
如果您能提供任何帮助或建议,我们将不胜感激。谢谢!

h7wcgrx3

h7wcgrx31#

在Realm论坛上有更详细的答案,但我将在这里包括它的要点。
问题中的代码有一些问题,但最大的罪魁祸首是重复迭代数据-这将导致延迟,但也因为它是在主线程将暂停UI。
基于这个问题,所有需要的点都可以通过单个查询来完成,这些查询不仅速度快,而且不需要迭代。像这样的东西

let sum: Double = realm.objects(WorkDay.self).sum(ofProperty: "hoursSingleTime")

下面是如何在getData函数中而不是在循环中实现这一点:

private var getData(inputDate: date, startDate: Date, endDate: Date) - > {
    var data = TimePeriodData()

    data.timePeriod = //use startDate and endDate
    data.date = inputDate
    data.localDate = workDayListBrain.getLocalDate(from: inputDate)

    data.singleTimeTotal: Double = realm.objects(WorkDay.self)
       .where { $0.startDate >= startDate && $0.endDate <= endDate }.sum(ofProperty: "hoursSingleTime")

   data.penaltyIncomeTotal: Double = realm.objects(WorkDay.self)
       .where { $0.startDate >= startDate && $0.endDate <= endDate }.sum(ofProperty: "penaltyIncome")

    //... the rest of the totals
    return data
}

另外两个问题出现了;一个是关于当对象有零值和中值时的平均值。让我在这里解决它们
平均值为零(其中WorkDay对象driveAway是可选的

//average driveAway taking nils into account
let results = realm.objects(WorkDay.self).where { $0.driveAway != nil }
let count = results.count
let countDouble = Double(count)
let total: Double = results.sum(ofProperty: "driveAway")
print("Average: \(total / countDouble)")

然后是中值(如有需要,根据上文合并零处理过滤器)

//find median of hoursSingleTime
let allResults = realm.objects(WorkDay.self).sorted(byKeyPath: "hoursSingleTime")
let allCount = allResults.count
let countIsEvenNum = allCount.isMultiple(of: 2) //even or odd number

if countIsEvenNum == true {
    print("even num")
    //get the objects values above and below the 'middle' index,
    //  add together and divide by 2. Leaving this as a code exercise for brevity

} else { //handle where the count of objects is odd so there's a middle object
    let middleIndex = allCount / 2
    let medianWork = allResults[middleIndex]
    print("Median is: \(medianWork.hoursSingleTime)")
}

相关问题