For completeness, here's a solution that explicitly makes use of the fact that you want to use name&planDate as both group identifier, and sort key.
You can make use of the Identifiable protocol, and build a struct with name&planDate (structs are almost free in Swift):
extension Item: Identifiable {
struct ID: Hashable, Comparable {
let name: String
let planDate: String
static func < (lhs: Item.ID, rhs: Item.ID) -> Bool {
(lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
}
}
var id: ID { ID(name: name, planDate: planDate) }
// this will come in handly later
init(id: ID, value: Float) {
self.init(value: value, name: id.name, planDate: id.planDate)
}
}
Then you can destructure the Item struct by its identifier, accumulate the values, and restructure it back:
let valueGroups = dataArray
.reduce(into: [:]) { groups, item in
// here we destructure the Item into id and value, and accumulate the value
groups[item.id, default: 0] += item.value
}
.sorted { $0.key < $1.key } // the key of the dictionary is the id, we sort by that
// and were we restructure it back
let result = valueGroups.map(Item.init(id:value:))
We can take it even further and refine the operations we need by extensing Sequence:
extension Sequence {
/// returns an array made of the sorted elements by the given key path
func sorted<T>(by keyPath: KeyPath<Element, T>) -> [Element] where T: Comparable {
sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
/// Accumulates the values by the specified key
func accumulated<K, T>(values: KeyPath<Element, T>,
by key: KeyPath<Element, K>) -> [K:T]
where K: Hashable, T: AdditiveArithmetic {
reduce(into: [:]) { $0[$1[keyPath: key], default: T.zero] += $1[keyPath: values] }
}
}
The two new additions, sort by a key path, and accumulate key paths by using another key, are independent enough to deserve function for they own, as they are generic enough to be reusable in other contexts.
The actual business logic becomes simple as
let result = dataArray
.accumulated(values: \.value, by: \.id)
.map(Item.init(id:value:))
.sorted(by: \.id)
Even if this solution is more verbose than the other one, it has the following advantages:
- clear separation of concerns
- breaking the code into smaller units, which can be independently unit tested
- code reusability
- simple caller code, easy to understand and review