Skip to content

Commit acb02c1

Browse files
Adds consolidation by KeyPath and Closure. Adds Swift documentation.
1 parent 1004a0a commit acb02c1

39 files changed

Lines changed: 3385 additions & 50 deletions

.jazzy.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module: Consolidate
2+
swift_build_tool: spm
3+
build_tool_arguments: [ "-Xswiftc", "-swift-version", "-Xswiftc", "5" ]
4+
github_url: https://github.com/SoftwareEngineerChris/Consolidate
5+

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Chris
3+
Copyright (c) 2019 Software Engineering Limited
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.0
1+
// swift-tools-version:5.1
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

README.md

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,94 @@
1-
# Consolidate
1+
# Consolidate
2+
3+
[![Build Status](https://app.bitrise.io/app/2401d8d24819bc28/status.svg?token=rpSxCpEdigqkV5zTb-dRog)](https://app.bitrise.io/app/2401d8d24819bc28)
4+
[![Docs](https://softwareengineerchris.github.io/Consolidate/badge.svg)](https://softwareengineerchris.github.io/MonetaryAmount)
5+
[![SPM](https://img.shields.io/badge/SPM-Supported-informational)](#)
6+
7+
## Installation
8+
9+
### Swift Package Manager
10+
```swift
11+
dependencies: [
12+
.package(url: "https://github.com/SoftwareEngineerChris/Consolidate.git", from: "1.0.0")
13+
]
14+
```
15+
16+
### Usage Example
17+
18+
#### Using KeyPath
19+
20+
```swift
21+
let allTaxes = [
22+
TaxAmount(name: "Import Tax", amount: 3.00),
23+
TaxAmount(name: "Sales Tax", amount: 1.75),
24+
TaxAmount(name: "Import Tax", amount: 2.30)
25+
]
26+
27+
let consolidatedTaxes = allTaxes.consolidated(by: \.name) {
28+
TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
29+
}
30+
31+
// Would result in:
32+
33+
let consolidatedTaxes = [
34+
TaxAmount(name: "Import Tax", amount: 5.30),
35+
TaxAmount(name: "Sales Tax", amount: 4.10)
36+
]
37+
```
38+
39+
#### Using Closures
40+
41+
```swift
42+
let allTaxes = [
43+
TaxAmount(name: "Import Tax", amount: 3.00),
44+
TaxAmount(name: "Sales Tax", amount: 1.75),
45+
TaxAmount(name: "Import Tax", amount: 2.30)
46+
]
47+
48+
let consolidatedTaxes = allTaxes.consolidated(by: { $0.name == $1.name }) {
49+
TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
50+
}
51+
52+
// Would result in:
53+
54+
let consolidatedTaxes = [
55+
TaxAmount(name: "Import Tax", amount: 5.30),
56+
TaxAmount(name: "Sales Tax", amount: 4.10)
57+
]
58+
```
59+
60+
#### Using Consolidatable Protocol
61+
62+
```swift
63+
struct TaxAmount: Consolidatable {
64+
65+
let name: String
66+
let amount: Decimal
67+
68+
var consolidationGroup: AnyHashable {
69+
return name
70+
}
71+
72+
func consolidate(with other: Self) -> Self {
73+
return .init(name: name, amount: amount + other.amount)
74+
}
75+
}
76+
```
77+
78+
The above implementation would consolidate like this:
79+
80+
81+
```swift
82+
let taxes = [
83+
TaxAmount(name: "Import Tax", amount: 3.00),
84+
TaxAmount(name: "Sales Tax", amount: 1.75),
85+
TaxAmount(name: "Import Tax", amount: 2.30)
86+
].consolidated()
87+
88+
// Results in:
89+
90+
let taxes = [
91+
TaxAmount(name: "Import Tax", amount: 5.30),
92+
TaxAmount(name: "Sales Tax", amount: 4.10)
93+
]
94+
```

Sources/Consolidate/Consolidatable.swift

Lines changed: 140 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,93 @@
88

99
import 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

Comments
 (0)