Skip to content

Commit 0b01e6f

Browse files
Added mapping functions to Sequence as alternatives to KeyPath (#1170)
1 parent a3990c3 commit 0b01e6f

File tree

3 files changed

+160
-2
lines changed

3 files changed

+160
-2
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
The changelog for **SwifterSwift**. Also see the [releases](https://github.com/SwifterSwift/SwifterSwift/releases) on GitHub.
44

55
## Upcoming Release
6-
76
### Added
87
- **NSView**
98
- Added `addArrangedSubviews(_ views: )` to add an array of views to the end of the arrangedSubviews array. [#1181](https://github.com/SwifterSwift/SwifterSwift/pull/1181) by [Roman Podymov](https://github.com/RomanPodymov)
109
- Added `removeArrangedSubviews` to remove all views in stack’s array of arranged subviews. [#1181](https://github.com/SwifterSwift/SwifterSwift/pull/1181) by [Roman Podymov](https://github.com/RomanPodymov)
10+
- **Sequence**
11+
- `sorted(by:)`, `sorted(by:with:)`, `sorted(by:and:)`, `sorted(by:and:and:)`, `sum(for:)`, `first(where:equals:)` now have alternatives that receive functions as parameters. This change maintains compatibility with KeyPath while making the methods more flexible. [#1170](https://github.com/SwifterSwift/SwifterSwift/pull/1170) by [MartonioJunior](https://github.com/MartonioJunior)
12+
13+
### Changed
14+
- **Sequence**
15+
- `sorted(by:)`, `sorted(by:with:)`, `sorted(by:and:)`, `sorted(by:and:and:)`, `sum(for:)`, `first(where:equals:)` now have alternatives that receive functions as parameters. This change maintains compatibility with KeyPath while making the methods more flexible. [#1170](https://github.com/SwifterSwift/SwifterSwift/pull/1170) by [MartonioJunior](https://github.com/MartonioJunior)
1116

1217
## [v6.1.1](https://github.com/SwifterSwift/SwifterSwift/releases/tag/6.1.1)
1318
### Added

Sources/SwifterSwift/SwiftStdlib/SequenceExtensions.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ public extension Sequence {
170170
return sorted { compare($0[keyPath: keyPath], $1[keyPath: keyPath]) }
171171
}
172172

173+
/// SwifterSwift: Return a sorted array based on a map function and a compare function.
174+
///
175+
/// - Parameter map: Function that defines the property to sort by.
176+
/// - Parameter compare: Comparison function that will determine the ordering.
177+
/// - Returns: The sorted array.
178+
func sorted<T>(by map: (Element) throws -> T, with compare: (T, T) -> Bool) rethrows -> [Element] {
179+
return try sorted { compare(try map($0), try map($1)) }
180+
}
181+
173182
/// SwifterSwift: Return a sorted array based on a key path.
174183
///
175184
/// - Parameter keyPath: Key path to sort by. The key path type must be Comparable.
@@ -178,6 +187,14 @@ public extension Sequence {
178187
return sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
179188
}
180189

190+
/// SwifterSwift: Return a sorted array based on a map function.
191+
///
192+
/// - Parameter map: Function that defines the property to sort by. The output type must be Comparable.
193+
/// - Returns: The sorted array.
194+
func sorted<T: Comparable>(by map: (Element) throws -> T) rethrows -> [Element] {
195+
return try sorted { try map($0) < map($1) }
196+
}
197+
181198
/// SwifterSwift: Returns a sorted sequence based on two key paths. The second one will be used in case the values
182199
/// of the first one match.
183200
///
@@ -194,6 +211,24 @@ public extension Sequence {
194211
}
195212
}
196213

214+
/// SwifterSwift: Returns a sorted sequence based on two map functions. The second one will be used in case the values
215+
/// of the first one match.
216+
///
217+
/// - Parameters:
218+
/// - map1: Map function to sort by. Output type must be Comparable.
219+
/// - map2: Map function to sort by in case the values of `map1` match. Output type must be Comparable.
220+
func sorted<T: Comparable, U: Comparable>(by map1: (Element) throws -> T,
221+
and map2: (Element) throws -> U) rethrows -> [Element] {
222+
return try sorted {
223+
let value10 = try map1($0)
224+
let value11 = try map1($1)
225+
if value10 != value11 {
226+
return value10 < value11
227+
}
228+
return try map2($0) < map2($1)
229+
}
230+
}
231+
197232
/// SwifterSwift: Returns a sorted sequence based on three key paths. Whenever the values of one key path match, the
198233
/// next one will be used.
199234
///
@@ -215,6 +250,32 @@ public extension Sequence {
215250
}
216251
}
217252

253+
/// SwifterSwift: Returns a sorted sequence based on three map functions. Whenever the values of one map function match, the
254+
/// next one will be used.
255+
///
256+
/// - Parameters:
257+
/// - map1: Map function to sort by. Output type must be Comparable.
258+
/// - map2: Map function to sort by in case the values of `map1` match. Output type must be Comparable.
259+
/// - map3: Map function to sort by in case the values of `map1` and `map2` match. Output type must be Comparable.
260+
func sorted<T: Comparable, U: Comparable, V: Comparable>(by map1: (Element) throws -> T,
261+
and map2: (Element) throws -> U,
262+
and map3: (Element) throws -> V) rethrows -> [Element] {
263+
return try sorted {
264+
let value10 = try map1($0)
265+
let value11 = try map1($1)
266+
if value10 != value11 {
267+
return value10 < value11
268+
}
269+
270+
let value20 = try map2($0)
271+
let value21 = try map2($1)
272+
if value20 != value21 {
273+
return value20 < value21
274+
}
275+
return try map3($0) < map3($1)
276+
}
277+
}
278+
218279
/// SwifterSwift: Sum of a `AdditiveArithmetic` property of each `Element` in a `Sequence`.
219280
///
220281
/// ["James", "Wade", "Bryant"].sum(for: \.count) -> 15
@@ -236,6 +297,17 @@ public extension Sequence {
236297
reduce(1) { $0 * map($1) }
237298
}
238299

300+
/// SwifterSwift: Sum of a `AdditiveArithmetic` property of each `Element` in a `Sequence`.
301+
///
302+
/// ["James", "Wade", "Bryant"].sum(for: \.count) -> 15
303+
///
304+
/// - Parameter map: Getter for the `AdditiveArithmetic` property.
305+
/// - Returns: The sum of the `AdditiveArithmetic` properties at `map`.
306+
func sum<T: AdditiveArithmetic>(for map: (Element) throws -> T) rethrows -> T {
307+
// Inspired by: https://swiftbysundell.com/articles/reducers-in-swift/
308+
return try reduce(.zero) { try $0 + map($1) }
309+
}
310+
239311
/// SwifterSwift: Returns the first element of the sequence with having property by given key path equals to given
240312
/// `value`.
241313
///
@@ -247,6 +319,18 @@ public extension Sequence {
247319
func first<T: Equatable>(where keyPath: KeyPath<Element, T>, equals value: T) -> Element? {
248320
return first { $0[keyPath: keyPath] == value }
249321
}
322+
323+
/// SwifterSwift: Returns the first element of the sequence with having property by given key path equals to given
324+
/// `value`.
325+
///
326+
/// - Parameters:
327+
/// - map: Function for `Element` to compare.
328+
/// - value: The value to compare with `Element` property.
329+
/// - Returns: The first element of the collection that has property by given map function equals to given `value` or
330+
/// `nil` if there is no such element.
331+
func first<T: Equatable>(where map: (Element) throws -> T, equals value: T) rethrows -> Element? {
332+
return try first { try map($0) == value }
333+
}
250334
}
251335

252336
public extension Sequence where Element: Equatable {

Tests/SwiftStdlibTests/SequenceExtensionsTests.swift

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,12 @@ final class SequenceExtensionsTests: XCTestCase {
134134
XCTAssertEqual(["James", "Wade", "Bryant"].sum(for: \.count), 15)
135135
XCTAssertEqual(["a", "b", "c", "d"].sum(for: \.count), 4)
136136
}
137-
137+
138+
func testMapFunctionSum() {
139+
XCTAssertEqual(["James", "Wade", "Bryant"].sum(for: { $0.count }), 15)
140+
XCTAssertEqual(["a", "b", "c", "d"].sum(for: { $0.count }), 4)
141+
}
142+
138143
func testProduct() {
139144
XCTAssertEqual([1, 2, 3, 4, 5].product(), 120)
140145
XCTAssertEqual([1.2, 2.3, 3.4, 4.5, 5.6].product(), 236.4768, accuracy: 0.001)
@@ -162,6 +167,24 @@ final class SequenceExtensionsTests: XCTestCase {
162167
let array2 = ["James", "Wade", "Bryant", ""]
163168
XCTAssertEqual(array2.sorted(by: \String.first, with: optionalCompare), ["Bryant", "James", "Wade", ""])
164169
}
170+
171+
func testMapFunctionSorted() {
172+
let array = ["James", "Wade", "Bryant"]
173+
XCTAssertEqual(array.sorted(by: { $0.count }, with: <), ["Wade", "James", "Bryant"])
174+
XCTAssertEqual(array.sorted(by: { $0.count }, with: >), ["Bryant", "James", "Wade"])
175+
176+
// Comparable version
177+
XCTAssertEqual(array.sorted(by: { $0.count }), ["Wade", "James", "Bryant"])
178+
179+
// Testing optional map function
180+
let optionalCompare = { (char1: Character?, char2: Character?) -> Bool in
181+
guard let char1 = char1, let char2 = char2 else { return false }
182+
return char1 < char2
183+
}
184+
185+
let array2 = ["James", "Wade", "Bryant", ""]
186+
XCTAssertEqual(array2.sorted(by: \String.first, with: optionalCompare), ["Bryant", "James", "Wade", ""])
187+
}
165188

166189
func testSortedByTwoKeyPaths() {
167190
let people = [
@@ -176,6 +199,20 @@ final class SequenceExtensionsTests: XCTestCase {
176199
]
177200
XCTAssertEqual(people.sorted(by: \.surname, and: \.age), expectedResult)
178201
}
202+
203+
func testSortedByTwoMapFunctions() {
204+
let people = [
205+
SimplePerson(forename: "Tom", surname: "James", age: 32),
206+
SimplePerson(forename: "Angeline", surname: "Wade", age: 57),
207+
SimplePerson(forename: "Max", surname: "James", age: 34)
208+
]
209+
let expectedResult = [
210+
SimplePerson(forename: "Tom", surname: "James", age: 32),
211+
SimplePerson(forename: "Max", surname: "James", age: 34),
212+
SimplePerson(forename: "Angeline", surname: "Wade", age: 57)
213+
]
214+
XCTAssertEqual(people.sorted(by: { $0.surname }, and: { $0.age }), expectedResult)
215+
}
179216

180217
func testSortedByThreeKeyPaths() {
181218
let people = [
@@ -192,6 +229,22 @@ final class SequenceExtensionsTests: XCTestCase {
192229
]
193230
XCTAssertEqual(people.sorted(by: \.surname, and: \.forename, and: \.age), expectedResult)
194231
}
232+
233+
func testSortedByThreeMapFunctions() {
234+
let people = [
235+
SimplePerson(forename: "Tom", surname: "James", age: 32),
236+
SimplePerson(forename: "Angeline", surname: "Wade", age: 57),
237+
SimplePerson(forename: "Max", surname: "James", age: 34),
238+
SimplePerson(forename: "Angeline", surname: "Wade", age: 82)
239+
]
240+
let expectedResult = [
241+
SimplePerson(forename: "Max", surname: "James", age: 34),
242+
SimplePerson(forename: "Tom", surname: "James", age: 32),
243+
SimplePerson(forename: "Angeline", surname: "Wade", age: 57),
244+
SimplePerson(forename: "Angeline", surname: "Wade", age: 82)
245+
]
246+
XCTAssertEqual(people.sorted(by: { $0.surname }, and: { $0.forename }, and: { $0.age }), expectedResult)
247+
}
195248

196249
func testFirstByKeyPath() {
197250
let array1 = [
@@ -208,4 +261,20 @@ final class SequenceExtensionsTests: XCTestCase {
208261

209262
XCTAssertNil(missingPerson)
210263
}
264+
265+
func testFirstByMapFunction() {
266+
let array1 = [
267+
Person(name: "John", age: 30, location: Location(city: "Boston")),
268+
Person(name: "Jan", age: 22, location: nil),
269+
Person(name: "Roman", age: 30, location: Location(city: "Moscow"))
270+
]
271+
272+
let first30Age = array1.first(where: { $0.age }, equals: 30)
273+
274+
XCTAssertEqual(first30Age, array1.first)
275+
276+
let missingPerson = array1.first(where: { $0.name }, equals: "Tom")
277+
278+
XCTAssertNil(missingPerson)
279+
}
211280
}

0 commit comments

Comments
 (0)