Zyntx
Zyntx

Reputation: 659

Sum of values from Core Data entity for a different Attribute value

The function calculates the sum of duration values for each title. This works as written but I feel it could be improved upon with either calculating the sum of durations during the fetch or better use of .map and .reduce and somehow removing the for-loop.

The entity "record" has attributes "endDate", "duration" and "title" were titles map one to many.

Any insights would be appreciated.

Here is a sample data set for "record" entity:

  1. [duration: 35.5, endDate: date(), title :"reading"],
  2. [duration: 25.0, endDate: date(), title :"reading"],
  3. [duration: 20, endDate: date(), title :"memrise"],
  4. [duration: 70, endDate: date(), title :"memrise"],
  5. [duration: 50, endDate: (other date), title :"memrise"]

for these, the predicate filters out the 5th entry and the function returns

(["reading","memrise"],[60.5, 90])

func getAllTitlesDurations(date: Date) -> (titles: [String], duration: [Double]) {

    // date extension to get first and last moment of date
    let dateDayStart = date.startOfDay
    let dateDayNext = date.endOfDay

    var durations : [Double] = []
    var titles : [String] = []

    // core data fetch, predicate to today 
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
        return ([],[])
    }
    let context = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: entityNames.Record.rawValue)
    fetchRequest.predicate = NSPredicate(format:"(endDate >= %@) AND (endDate < %@)", dateDayStart as CVarArg, dateDayNext as CVarArg)

    do {
        let records = try context.fetch(fetchRequest) as! [Record]
        // create unique titles
        titles = Array(Set(records.map{$0.title!}))
        for m in titles {
            durations.append(records
            .filter{$0.title == m}
            .reduce(0){$0 + $1.duration})
        }
        //MARK : Check outputs
        print (durations)
        print(titles)
    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
        return ([],[])
    }
    return (titles, durations)
}

Upvotes: 0

Views: 452

Answers (1)

David Berry
David Berry

Reputation: 41226

You could do it all in the query using group by and a dictionary result. Simpler perhaps is to just collapse the n^^2 algorithm used to compute the duration into:

let result = records.reduce(into: [:]) { acc, record in
    acc[record.title, default:0.0] += record.duration
}

That will build a dictionary of title to total duration in close to order n time.

If you really want two arrays, you can add:

.reduce(into: ([String](), [Double]())) {
    $0.0.append($1.key)
    $0.1.append($1.value)
}

Upvotes: 2

Related Questions