0

I have a struct that contains a date and quantity. I am trying to sum the quantity by either the month and year.

I thought of using Dictionary(grouping, by) but the problem is I am able to only group by the date which is not what I want. Essentially, I need to group by the date and then do a reduce operation. Not sure how to accomplish this. I can always use the brut force method of looping through the array and aggregating the values, but I sure think there is a better way using map/reduce. Any ideas?

Appreciate your help!

struct Transaction {
    var date = Date()
    var quantity: Double
}

here is what I am looking for: An array (that is sorted) or a dictionary with [month : sum of quantity] for that month

RXP
  • 517
  • 4
  • 18

1 Answers1

1

You can reduce your transactions and check if each element's year and month date components are equal to the desired month/year, if true sum it up otherwise return the last total:

let year = 2020
let month = 11
let yearMonth = DateComponents(year: year, month: month)
let total = transactions.reduce(0) {
    Calendar.current.dateComponents([.year, .month], from: $1.date) == yearMonth ? $0 + $1.quantity : $0
}

If you want to filter the transactions instead of just sum:

let filtered = transactions.filter {
    Calendar.current.dateComponents([.year, .month], from: $0.date) == yearMonth
}

If your original transactions is not sorted just sort it after filtering:

.sorted { $0.date < $1.date }

To sum all quantities in your transactions or part of it:

let sum = filtered.reduce(0) {$0 + $1.quantity}

To get the total quantity for each month:

extension Date {
    var startOfMonth: Date { Calendar.current.dateComponents([.calendar, .year, .month], from: self).date! }
}

let monthTotals: [Date: Double] = transactions.reduce(into: [:]) {
    $0[$1.date.startOfMonth, default: 0] +=  $1.quantity
}

or grouping your transactions by month:

let grouped: [[Transaction]] = transactions
    .sorted { $0.date < $1.date }
    .reduce(into: []) {
        if $0.last?.last?.date.startOfMonth == $1.date.startOfMonth {
            $0[$0.indices.last!].append($1)
        } else {
            $0.append([$1])
        }
    }
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Leo, thanks for your reply. This works for a specific month. Is there a way to get an array by month with the quantities summed by month? – RXP Nov 15 '20 at 02:02
  • What do you mean all months in a year? – Leo Dabus Nov 15 '20 at 02:39
  • Not just for 1 year but all the data. The data I have spans multiple years and I am looking to get the sum of quantity by each month in an array. – RXP Nov 15 '20 at 02:58