Skip to content

Commit fccc0e8

Browse files
Merge pull request #1 from SoftwareEngineerChris/consolidate/into-single-master
Adds throwable method consolidatedIntoSingle and updates Package.swift with platforms and Swift versions
2 parents 7e61a21 + 7f4a8c9 commit fccc0e8

File tree

28 files changed

+1029
-93
lines changed

28 files changed

+1029
-93
lines changed

Gemfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6+
7+
gem "jazzy"

Gemfile.lock

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
CFPropertyList (3.0.2)
5+
activesupport (4.2.11.3)
6+
i18n (~> 0.7)
7+
minitest (~> 5.1)
8+
thread_safe (~> 0.3, >= 0.3.4)
9+
tzinfo (~> 1.1)
10+
algoliasearch (1.27.3)
11+
httpclient (~> 2.8, >= 2.8.3)
12+
json (>= 1.5.1)
13+
atomos (0.1.3)
14+
claide (1.0.3)
15+
cocoapods (1.9.3)
16+
activesupport (>= 4.0.2, < 5)
17+
claide (>= 1.0.2, < 2.0)
18+
cocoapods-core (= 1.9.3)
19+
cocoapods-deintegrate (>= 1.0.3, < 2.0)
20+
cocoapods-downloader (>= 1.2.2, < 2.0)
21+
cocoapods-plugins (>= 1.0.0, < 2.0)
22+
cocoapods-search (>= 1.0.0, < 2.0)
23+
cocoapods-stats (>= 1.0.0, < 2.0)
24+
cocoapods-trunk (>= 1.4.0, < 2.0)
25+
cocoapods-try (>= 1.1.0, < 2.0)
26+
colored2 (~> 3.1)
27+
escape (~> 0.0.4)
28+
fourflusher (>= 2.3.0, < 3.0)
29+
gh_inspector (~> 1.0)
30+
molinillo (~> 0.6.6)
31+
nap (~> 1.0)
32+
ruby-macho (~> 1.4)
33+
xcodeproj (>= 1.14.0, < 2.0)
34+
cocoapods-core (1.9.3)
35+
activesupport (>= 4.0.2, < 6)
36+
algoliasearch (~> 1.0)
37+
concurrent-ruby (~> 1.1)
38+
fuzzy_match (~> 2.0.4)
39+
nap (~> 1.0)
40+
netrc (~> 0.11)
41+
typhoeus (~> 1.0)
42+
cocoapods-deintegrate (1.0.4)
43+
cocoapods-downloader (1.3.0)
44+
cocoapods-plugins (1.0.0)
45+
nap
46+
cocoapods-search (1.0.0)
47+
cocoapods-stats (1.1.0)
48+
cocoapods-trunk (1.5.0)
49+
nap (>= 0.8, < 2.0)
50+
netrc (~> 0.11)
51+
cocoapods-try (1.2.0)
52+
colored2 (3.1.2)
53+
concurrent-ruby (1.1.6)
54+
escape (0.0.4)
55+
ethon (0.12.0)
56+
ffi (>= 1.3.0)
57+
ffi (1.13.1)
58+
fourflusher (2.3.1)
59+
fuzzy_match (2.0.4)
60+
gh_inspector (1.1.3)
61+
httpclient (2.8.3)
62+
i18n (0.9.5)
63+
concurrent-ruby (~> 1.0)
64+
jazzy (0.13.4)
65+
cocoapods (~> 1.5)
66+
mustache (~> 1.1)
67+
open4
68+
redcarpet (~> 3.4)
69+
rouge (>= 2.0.6, < 4.0)
70+
sassc (~> 2.1)
71+
sqlite3 (~> 1.3)
72+
xcinvoke (~> 0.3.0)
73+
json (2.3.0)
74+
liferaft (0.0.6)
75+
minitest (5.14.1)
76+
molinillo (0.6.6)
77+
mustache (1.1.1)
78+
nanaimo (0.2.6)
79+
nap (1.1.0)
80+
netrc (0.11.0)
81+
open4 (1.3.4)
82+
redcarpet (3.5.0)
83+
rouge (3.20.0)
84+
ruby-macho (1.4.0)
85+
sassc (2.4.0)
86+
ffi (~> 1.9)
87+
sqlite3 (1.4.2)
88+
thread_safe (0.3.6)
89+
typhoeus (1.4.0)
90+
ethon (>= 0.9.0)
91+
tzinfo (1.2.7)
92+
thread_safe (~> 0.1)
93+
xcinvoke (0.3.0)
94+
liferaft (~> 0.0.6)
95+
xcodeproj (1.17.0)
96+
CFPropertyList (>= 2.3.3, < 4.0)
97+
atomos (~> 0.1.3)
98+
claide (>= 1.0.2, < 2.0)
99+
colored2 (~> 3.1)
100+
nanaimo (~> 0.2.6)
101+
102+
PLATFORMS
103+
ruby
104+
105+
DEPENDENCIES
106+
jazzy
107+
108+
BUNDLED WITH
109+
2.1.4

Package.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
// swift-tools-version:5.1
1+
// swift-tools-version:5.2
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
55

66
let package = Package(
77
name: "Consolidate",
8+
platforms: [.macOS(.v10_10),
9+
.iOS(.v8),
10+
.tvOS(.v9),
11+
.watchOS(.v2)],
812
products: [
913
.library(
1014
name: "Consolidate",
@@ -19,5 +23,6 @@ let package = Package(
1923
.testTarget(
2024
name: "ConsolidateTests",
2125
dependencies: ["Consolidate"]),
22-
]
26+
],
27+
swiftLanguageVersions: [.v5]
2328
)

README.md

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ dependencies: [
1313
]
1414
```
1515

16-
### Usage Example
16+
## Usage Example
1717

18-
#### Using KeyPath
18+
### Using KeyPath
1919

2020
```swift
2121
let allTaxes = [
@@ -36,7 +36,7 @@ let consolidatedTaxes = [
3636
]
3737
```
3838

39-
#### Using Closures
39+
### Using Closures
4040

4141
```swift
4242
let allTaxes = [
@@ -57,7 +57,7 @@ let consolidatedTaxes = allTaxes.consolidated(by: { $0.name == $1.name }) {
5757
]
5858
```
5959

60-
#### Using Consolidatable Protocol
60+
### Using Consolidatable Protocol
6161

6262
```swift
6363
struct TaxAmount: Consolidatable {
@@ -92,3 +92,46 @@ let taxes = [
9292
TaxAmount(name: "Sales Tax", amount: 4.10)
9393
]
9494
```
95+
96+
### Consolidate into single item (throws if unable to)
97+
98+
There may be times where you want to consolidate a collection of elements into a single element.
99+
For this, there exists the array extension `consolidatedIntoSingle`. It is functionally similar to the other
100+
consolidation methods, except its return type is of type `Element` rather than `[Element]`. This means that
101+
the collection has to be consolidatable to a single element. If the collection can't be consolidated to a single
102+
element, then the method will throw the error `ConsolidationError.couldNotBeConolidatedIntoSingleElement`.
103+
104+
```swift
105+
let allTaxes = [
106+
TaxAmount(name: "Import Tax", amount: 3.00),
107+
TaxAmount(name: "Sales Tax", amount: 1.75),
108+
TaxAmount(name: "Import Tax", amount: 2.30)
109+
]
110+
111+
// The line below would throw `ConsolidationError.couldNotBeConolidatedIntoSingleElement`,
112+
// since the taxes can't be consolidated into a single tax when using the name key-path.
113+
114+
let consolidatedTax = try allTaxes.consolidatedIntoSingle(by: \.name) {
115+
TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
116+
}
117+
```
118+
119+
Whereas:
120+
121+
```swift
122+
let allTaxes = [
123+
TaxAmount(name: "Import Tax", amount: 3.00),
124+
TaxAmount(name: "Import Tax", amount: 2.30)
125+
]
126+
127+
let consolidatedTax = try allTaxes.consolidatedIntoSingle(by: \.name) {
128+
TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
129+
}
130+
131+
// Since all taxes have the name "Import Tax", they can be consolidated into a single tax
132+
// when using the name key-path. The result would be:
133+
134+
let consolidatedTax = [
135+
TaxAmount(name: "Import Tax", amount: 5.30)
136+
]
137+
```

Sources/Consolidate/Consolidatable.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,59 @@ public extension Array {
5454

5555
return consolidated(by: { $0[keyPath: keyPath] == $1[keyPath: keyPath] }, using: consolidating)
5656
}
57+
58+
59+
/// Consolidates (reduces) an array of `Elements`, by a `KeyPath` using a given closure, into a single element.
60+
///
61+
/// ### Example
62+
///
63+
/// let allTaxes = [
64+
/// TaxAmount(name: "Import Tax", amount: 3.00),
65+
/// TaxAmount(name: "Import Tax", amount: 2.30)
66+
/// ]
67+
///
68+
/// // The next line would result in TaxAmount(name: "Import Tax", amount: 5.30)
69+
///
70+
/// let consolidatedTaxes = allTaxes.consolidated(by: \.name) {
71+
/// TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
72+
/// }
73+
///
74+
/// Since the `TaxAmount` type is consolidated by name, the two entries for _"Import Tax"_ have been consolidated
75+
/// into a single `TaxAmount` where their `amount` values have been added.
76+
///
77+
///
78+
/// let allTaxes = [
79+
/// TaxAmount(name: "Import Tax", amount: 3.00),
80+
/// TaxAmount(name: "Sales Tax", amount: 1.75),
81+
/// TaxAmount(name: "Import Tax", amount: 2.30)
82+
/// ]
83+
///
84+
/// // The next line would throw
85+
///
86+
/// let consolidatedTaxes = allTaxes.consolidated(by: \.name) {
87+
/// TaxAmount(tax: $0.name, amount: $0.amount + $1.amount)
88+
/// }
89+
///
90+
/// Since the `TaxAmount` entries would consolidate to two elements (Import Tax and Sales Tax) the example above would throw
91+
/// the error `ConsolidationError.couldNotBeConolidatedIntoSingleElement`.
92+
///
93+
/// - Parameters:
94+
/// - keyPath: The key path to the property to use as a consolidation group.
95+
/// - consolidating: The closure used to consolidate two `Element` values into a single `Element` value.
96+
///
97+
/// - Returns: A single element representing the consolidation by a `KeyPath` using the given `consolidating` closure.
98+
/// - Throws: `ConsolidationError.couldNotBeConolidatedIntoSingleElement` error if the array does not consolidate into a single element.
99+
@inlinable
100+
func consolidatedIntoSingle<GroupType: Hashable>(by keyPath: KeyPath<Element, GroupType>, using consolidating: Consolidate) throws -> Element {
101+
102+
let consolidatedArray = consolidated(by: keyPath, using: consolidating)
103+
104+
guard consolidatedArray.count == 1, let consolidatedSingle = consolidatedArray.first else {
105+
throw ConsolidationError.couldNotBeConolidatedIntoSingleElement
106+
}
107+
108+
return consolidatedSingle
109+
}
57110

58111
/// Consolidates (reduces) an array of `Elements` grouped by the result of a closure
59112
/// combined using another closure.
@@ -102,6 +155,14 @@ public extension Array {
102155
return result + [element]
103156
}
104157
}
158+
159+
// MARK: Errors
160+
161+
/// Errors that can occur during consolidation
162+
enum ConsolidationError: Error {
163+
/// Thrown when attempting to concolidate to a single element, but the collection can not be consolidated to a single element.
164+
case couldNotBeConolidatedIntoSingleElement
165+
}
105166
}
106167

107168
/// A type implementing `Consolidatable` allows an array of its values to be combined by

Tests/ConsolidateTests/ConsolidatableTests.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,40 @@ final class ConsolidatableTests: XCTestCase {
8484
TaxAmount(tax: "Sales Tax", amount: 1.75)
8585
])
8686
}
87+
88+
// MARK: consolidatedIntoSingle
89+
90+
func test_consolidatedIntoSingle_canBeConsolidatedIntoSingle_returnsConsolidation() throws {
91+
92+
let taxes = try [
93+
TaxAmount(tax: "Import Tax", amount: 3.00),
94+
TaxAmount(tax: "Import Tax", amount: 2.30)
95+
].consolidatedIntoSingle(by: \.tax) {
96+
TaxAmount(tax: $0.tax, amount: $0.amount + $1.amount)
97+
}
98+
99+
XCTAssertEqual(taxes, TaxAmount(tax: "Import Tax", amount: 5.30))
100+
}
87101

102+
func test_consolidatedIntoSingle_canNotBeConsolidatedIntoSingle_throws() throws {
103+
104+
XCTAssertThrowsError(try [
105+
TaxAmount(tax: "Import Tax", amount: 3.00),
106+
TaxAmount(tax: "Sales Tax", amount: 1.75),
107+
TaxAmount(tax: "Import Tax", amount: 2.30)
108+
].consolidatedIntoSingle(by: \.tax) {
109+
TaxAmount(tax: $0.tax, amount: $0.amount + $1.amount)
110+
})
111+
}
112+
88113
static var allTests = [
89114
("test_emptyArray_returnsEmptyArray", test_emptyArray_returnsEmptyArray),
90115
("test_usingConsolidatableProtocol_noConsolidabaleValues_doesNotConsolidate", test_usingConsolidatableProtocol_noConsolidabaleValues_doesNotConsolidate),
91116
("test_usingConsolidatableProtocol_consolidabaleValues_consolidates", test_usingConsolidatableProtocol_consolidabaleValues_consolidates),
92117
("test_byKeyPath_consolidates", test_byKeyPath_consolidates),
93-
("test_byClosure_consolidates", test_byClosure_consolidates)
118+
("test_byClosure_consolidates", test_byClosure_consolidates),
119+
("test_consolidatedIntoSingle_canBeConsolidatedIntoSingle_returnsConsolidation", test_consolidatedIntoSingle_canBeConsolidatedIntoSingle_returnsConsolidation),
120+
("test_consolidatedIntoSingle_canNotBeConsolidatedIntoSingle_throws", test_consolidatedIntoSingle_canNotBeConsolidatedIntoSingle_throws),
94121
]
95122

96123
// MARK: Test Fixtures

docs/Extensions.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<li class="nav-group-task">
3535
<a href="Extensions/Array.html">Array</a>
3636
</li>
37+
<li class="nav-group-task">
38+
<a href="Extensions/Array/ConsolidationError.html">– ConsolidationError</a>
39+
</li>
3740
</ul>
3841
</li>
3942
<li class="nav-group-name">
@@ -76,8 +79,8 @@ <h1>Extensions</h1>
7679
<h4>Declaration</h4>
7780
<div class="language">
7881
<p class="aside-title">Swift</p>
79-
<pre class="highlight swift"><code><span class="kd">@frozen</span>
80-
<span class="kd">public</span> <span class="kd">extension</span> <span class="kt">Array</span></code></pre>
82+
<pre class="highlight swift"><code><span class="kd">public</span> <span class="kd">extension</span> <span class="kt">Array</span></code></pre>
83+
<pre class="highlight swift"><code><span class="kd">public</span> <span class="kd">extension</span> <span class="kt">Array</span> <span class="k">where</span> <span class="kt">Element</span><span class="p">:</span> <span class="kt"><a href="Protocols/Consolidatable.html">Consolidatable</a></span></code></pre>
8184

8285
</div>
8386
</div>
@@ -89,8 +92,8 @@ <h4>Declaration</h4>
8992
</section>
9093
</section>
9194
<section id="footer">
92-
<p>&copy; 2019 <a class="link" href="" target="_blank" rel="external"></a>. All rights reserved. (Last updated: 2019-10-02)</p>
93-
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.11.2</a>, a <a class="link" href="https://realm.io" target="_blank" rel="external">Realm</a> project.</p>
95+
<p>&copy; 2020 <a class="link" href="" target="_blank" rel="external"></a>. All rights reserved. (Last updated: 2020-06-27)</p>
96+
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.13.4</a>, a <a class="link" href="https://realm.io" target="_blank" rel="external">Realm</a> project.</p>
9497
</section>
9598
</article>
9699
</div>

0 commit comments

Comments
 (0)