为了完整起见,这里有一个解决方案,它明确地利用了您希望同时使用 name&planDate 作为组标识符和排序键这一事实。
您可以使用Identifiable 协议,并使用 name&planDate 构建一个结构(结构在 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)
}
}
然后你可以通过它的标识符来解构Item 结构,累积值,然后重新构造它:
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:))
我们可以更进一步,通过扩展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] }
}
}
这两个新增功能,按键路径排序,并通过使用另一个键来累积键路径,它们足够独立,值得拥有它们自己的功能,因为它们足够通用,可以在其他上下文中重用。
实际的业务逻辑就变得简单了
let result = dataArray
.accumulated(values: \.value, by: \.id)
.map(Item.init(id:value:))
.sorted(by: \.id)
即使这个解决方案比另一个更冗长,它也有以下优点:
- 清晰的关注点分离
- 将代码分解成更小的单元,可以独立进行单元测试
- 代码可重用性
- 简单的调用者代码,易于理解和查看