88
99import Foundation
1010
11- public protocol Consolidatable {
11+ public extension Array {
1212
13- var consolidationGroup : AnyHashable { get }
13+ // MARK: Closure Type Aliases
1414
15- func consolidate( with other: Self ) -> Self
15+ /// Closure used to consolidate two `Element` values.
16+ typealias Consolidate = ( _ first: Element , _ second: Element ) -> Element
1617
17- }
18-
19- public extension Array where Element: Consolidatable {
18+ /// Closure used to decide whether two `Element` values should be consolidated.
19+ typealias ShouldConsolidate = ( Element , Element ) -> Bool
2020
21- func consolidated( ) -> Self {
21+ // MARK: Consolidation
22+
23+ /// Consolidates (reduces) an array of `Elements` by a `KeyPath` using a given closure.
24+ ///
25+ /// ### Example
26+ ///
27+ /// let allTaxes = [
28+ /// TaxAmount(name: "Import Tax", amount: 3.00),
29+ /// TaxAmount(name: "Sales Tax", amount: 1.75),
30+ /// TaxAmount(name: "Import Tax", amount: 2.30)
31+ /// ]
32+ ///
33+ /// let consolidatedTaxes = allTaxes.consolidated(by: \.name) {
34+ /// TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
35+ /// }
36+ ///
37+ /// // Would result in:
38+ ///
39+ /// let consolidatedTaxes = [
40+ /// TaxAmount(name: "Import Tax", amount: 5.30),
41+ /// TaxAmount(name: "Sales Tax", amount: 4.10)
42+ /// ]
43+ ///
44+ /// Since the `TaxAmount` type is consolidated by name, the two entries for _"Sales Tax"_ have been consolidated
45+ /// into a single `TaxAmount` where their `amount` values have been added.
46+ ///
47+ /// - Parameters:
48+ /// - keyPath: The key path to the property to use as a consolidation group.
49+ /// - consolidating: The closure used to consolidate two `Element` values into a single `Element` value.
50+ ///
51+ /// - Returns: A new array of elements that have been consolidated by a `KeyPath` using the given `consolidating` closure.
52+ @inlinable
53+ func consolidated< GroupType: Hashable > ( by keyPath: KeyPath < Element , GroupType > , using consolidating: Consolidate ) -> Self {
54+
55+ return consolidated ( by: { $0 [ keyPath: keyPath] == $1 [ keyPath: keyPath] } , using: consolidating)
56+ }
57+
58+ /// Consolidates (reduces) an array of `Elements` grouped by the result of a closure
59+ /// combined using another closure.
60+ ///
61+ /// ### Example
62+ ///
63+ /// let allTaxes = [
64+ /// TaxAmount(name: "Import Tax", amount: 3.00),
65+ /// TaxAmount(name: "Sales Tax", amount: 1.75),
66+ /// TaxAmount(name: "Import Tax", amount: 2.30)
67+ /// ]
68+ ///
69+ /// let consolidatedTaxes = allTaxes.consolidated(by: { $0.name == $1.name }) {
70+ /// TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
71+ /// }
72+ ///
73+ /// // Would result in:
74+ ///
75+ /// let consolidatedTaxes = [
76+ /// TaxAmount(name: "Import Tax", amount: 5.30),
77+ /// TaxAmount(name: "Sales Tax", amount: 4.10)
78+ /// ]
79+ ///
80+ /// Since the `TaxAmount` type is consolidated by name, the two entries for _"Sales Tax"_ have been consolidated
81+ /// into a single `TaxAmount` where their `amount` values have been added.
82+ ///
83+ /// - Parameters:
84+ /// - comparison: The closure used to decide whether two `Element` values should be consolidated.
85+ /// - consolidating: The closure used to consolidate two `Element` values into a single `Element` value.
86+ ///
87+ /// - Returns: A new array of elements that have been grouped by the result of a closure combined using another closure.
88+ @inlinable
89+ func consolidated( by comparison: ShouldConsolidate , using consolidating: Consolidate ) -> Self {
2290
2391 return reduce ( [ ] ) { ( result, element) -> [ Element ] in
2492
25- if let existingGroupIndex = result. firstIndex ( where: { $0 . consolidationGroup == element. consolidationGroup } ) {
93+ if let existingGroupIndex = result. firstIndex ( where: { comparison ( $0 , element) } ) {
2694
2795 var mutableResult = result
2896
29- mutableResult [ existingGroupIndex] = result [ existingGroupIndex] . consolidate ( with : element)
97+ mutableResult [ existingGroupIndex] = consolidating ( result [ existingGroupIndex] , element)
3098
3199 return mutableResult
32100 }
@@ -35,3 +103,66 @@ public extension Array where Element: Consolidatable {
35103 }
36104 }
37105}
106+
107+ /// A type implementing `Consolidatable` allows an array of its values to be combined by
108+ /// `consolidationGroup` into a new array using the type's `consolidate(with:)` function.
109+ public protocol Consolidatable {
110+
111+ /// The group in which an element should belong to when consolidated
112+ var consolidationGroup : AnyHashable { get }
113+
114+ /// The implementation of how two values of the same type are consolidated into a single value of that type
115+ /// - parameters:
116+ /// - other: Another value of the same type.
117+ /// - returns: A value representing the consolidation of `self` and `other`
118+ func consolidate( with other: Self ) -> Self
119+ }
120+
121+ public extension Array where Element: Consolidatable {
122+
123+ // MARK: Where Element type implements Consolidatable
124+
125+ /// Consolidates (reduces) an array of `Elements` that implement the `Consolidatable` protocol.
126+ ///
127+ /// ### Example
128+ ///
129+ /// struct TaxAmount: Consolidatable {
130+ ///
131+ /// let name: String
132+ /// let amount: Decimal
133+ ///
134+ /// var consolidationGroup: AnyHashable {
135+ /// return name
136+ /// }
137+ ///
138+ /// func consolidate(with other: Self) -> Self {
139+ /// return .init(name: name, amount: amount + other.amount)
140+ /// }
141+ /// }
142+ ///
143+ /// The above implementation would consolidate like this:
144+ ///
145+ /// let taxes = [
146+ /// TaxAmount(name: "Import Tax", amount: 3.00),
147+ /// TaxAmount(name: "Sales Tax", amount: 1.75),
148+ /// TaxAmount(name: "Import Tax", amount: 2.30)
149+ /// ].consolidated()
150+ ///
151+ /// // Results in:
152+ ///
153+ /// let taxes = [
154+ /// TaxAmount(name: "Import Tax", amount: 5.30),
155+ /// TaxAmount(name: "Sales Tax", amount: 4.10)
156+ /// ]
157+ ///
158+ /// Since the `TaxAmount` type is consolidated by name, the two entries for _"Sales Tax"_ have been consolidated
159+ /// into a single `TaxAmount` where their `amount` values have been added.
160+ ///
161+ /// - returns: A new array of elements that have been consolidated by group (`consolidationGroup`) using
162+ /// the implementation of `consolidate(with:)`
163+ @inlinable
164+ func consolidated( ) -> Self {
165+
166+ return consolidated ( by: { $0. consolidationGroup == $1. consolidationGroup } , using: { $0. consolidate ( with: $1) } )
167+ }
168+ }
0 commit comments