Skip to content

Commit 4190dec

Browse files
author
Dmitriy Ignatyev
committed
added partitionMap
1 parent c3b3acb commit 4190dec

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

Sources/Algorithms/PartitionMap.swift

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2020-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
//===----------------------------------------------------------------------===//
13+
// PartitionMapResult2
14+
//===----------------------------------------------------------------------===//
15+
16+
// `PartitionMapResult` Types are needed because of current generic limitations.
17+
// It is separated into public struct and internal enum. Such design has benefits
18+
// in comparison to plain enum:
19+
// - prevent its usage as a general purpose Either / OneOf Type – there are no
20+
// public properties which makes it useless outside
21+
// the library anywhere except with `partitionMap()` function.
22+
// - allows to rename `first`, `second` and `third` without source breakage .
23+
// If something more suitable will be found then old static initializers can be
24+
// deprecated with introducing new ones.
25+
26+
public struct PartitionMapResult2<A, B> {
27+
@usableFromInline
28+
internal let oneOf: _PartitionMapResult2<A, B>
29+
30+
@usableFromInline
31+
internal init(oneOf: _PartitionMapResult2<A, B>) { self.oneOf = oneOf }
32+
33+
@inlinable
34+
public static func first(_ value: A) -> Self {
35+
Self(oneOf: .first(value))
36+
}
37+
38+
@inlinable
39+
public static func second(_ value: B) -> Self {
40+
Self(oneOf: .second(value))
41+
}
42+
}
43+
44+
@usableFromInline
45+
internal enum _PartitionMapResult2<A, B> {
46+
case first(A)
47+
case second(B)
48+
}
49+
50+
//===----------------------------------------------------------------------===//
51+
// PartitionMapResult3
52+
//===----------------------------------------------------------------------===//
53+
54+
public struct PartitionMapResult3<A, B, C> {
55+
@usableFromInline
56+
internal let oneOf: _PartitionMapResult3<A, B, C>
57+
58+
@usableFromInline
59+
internal init(oneOf: _PartitionMapResult3<A, B, C>) {
60+
self.oneOf = oneOf
61+
}
62+
63+
@inlinable
64+
public static func first(_ value: A) -> Self {
65+
Self(oneOf: .first(value))
66+
}
67+
68+
@inlinable
69+
public static func second(_ value: B) -> Self {
70+
Self(oneOf: .second(value))
71+
}
72+
73+
@inlinable
74+
public static func third(_ value: C) -> Self {
75+
Self(oneOf: .third(value))
76+
}
77+
}
78+
79+
@usableFromInline
80+
internal enum _PartitionMapResult3<A, B, C> {
81+
case first(A)
82+
case second(B)
83+
case third(C)
84+
}
85+
86+
//===----------------------------------------------------------------------===//
87+
// partitionMap()
88+
//===----------------------------------------------------------------------===//
89+
90+
extension Sequence {
91+
/// Allows to separate elements into distinct groups while applying a transformation to each element
92+
///
93+
/// This method do the same as `partitioned(by:)` but with an added map step baked in for
94+
/// ergonomic reasons.
95+
///
96+
/// The `partitionMap` applies the given closure to each element of the collection and divides the
97+
/// results into two groups based on the transformation's output.
98+
/// The closure returns a `PartitionMapResult`, which indicates whether the result should be
99+
/// included in the first group or in the second.
100+
///
101+
/// In this example, `partitionMap(_:)` is used to separate an array of `any Error` elements into
102+
/// two arrays while also transforming the type from
103+
/// `any Error` to `URLSessionError` for the first group.
104+
/// ```
105+
/// func handle(errors: [any Error]) {
106+
/// let (recoverableErrors, unrecoverableErrors) = errors
107+
/// .partitionMap { error -> PartitionMapResult2<URLSessionError, any Error> in
108+
/// switch error {
109+
/// case let urlError as URLSessionError: return .first(urlError)
110+
/// default: return .second(error)
111+
/// }
112+
/// }
113+
/// // recoverableErrors Type is Array<URLSessionError>
114+
/// // unrecoverableErrors Type is Array<any Error>
115+
/// }
116+
/// ```
117+
///
118+
/// - Parameters:
119+
/// - transform: A mapping closure. `transform` accepts an element of this sequence as its
120+
/// parameter and returns a `PartitionMapResult` with a transformed value, representing
121+
/// membership to either the first or second group with elements of the original or of a different type.
122+
///
123+
/// - Returns: Two arrays, with elements from the first or second group appropriately.
124+
///
125+
/// - Throws: Rethrows any errors produced by the `transform` closure.
126+
///
127+
/// - Complexity: O(*n*), where *n* is the length of the collection.
128+
@inlinable
129+
public func partitionMap<A, B>(
130+
_ transform: (Element) throws -> PartitionMapResult2<A, B>
131+
) rethrows -> ([A], [B]) {
132+
var groupA: [A] = []
133+
var groupB: [B] = []
134+
135+
for element in self {
136+
switch try transform(element).oneOf {
137+
case .first(let a): groupA.append(a)
138+
case .second(let b): groupB.append(b)
139+
}
140+
}
141+
142+
return (groupA, groupB)
143+
}
144+
145+
/// Allows to separate elements into distinct groups while applying a transformation to each element
146+
///
147+
/// This method do the same as `partitioned(by:)` but with an added map step baked in for
148+
/// ergonomic reasons.
149+
///
150+
/// The `partitionMap` applies the given closure to each element of the collection and divides the
151+
/// results into distinct groups based on the transformation's output.
152+
/// The closure returns a `PartitionMapResult`, which indicates whether the result should be
153+
/// included in the first , second or third group.
154+
///
155+
/// In this example, `partitionMap(_:)` is used to separate an array of `any Error` elements into
156+
/// three arrays while also transforming the type from
157+
/// `any Error` to `URLSessionError` for the first and second groups.
158+
/// ```
159+
/// func handle(errors: [any Error]) {
160+
/// let (recoverableErrors, unrecoverableErrors, unknownErrors) = errors
161+
/// .partitionMap { error -> PartitionMapResult3<URLSessionError, any Error> in
162+
/// switch error {
163+
/// case let urlError as URLSessionError:
164+
/// return recoverableURLErrorCodes.contains(urlError.code) ? .first(urlError) : .second(urlError)
165+
/// default:
166+
/// return .third(error)
167+
/// }
168+
/// }
169+
/// // recoverableErrors Type is Array<URLSessionError>
170+
/// // unrecoverableErrors Type is Array<URLSessionError>
171+
/// // unknownErrors Type is Array<any Error>
172+
/// }
173+
/// ```
174+
///
175+
/// - Parameters:
176+
/// - transform: A mapping closure. `transform` accepts an element of this sequence as its
177+
/// parameter and returns a `PartitionMapResult` with a transformed value, representing
178+
/// membership to either first, second or third group with elements of the original or of a different type.
179+
///
180+
/// - Returns: Three arrays, with elements from the first, second or third group appropriately.
181+
///
182+
/// - Throws: Rethrows any errors produced by the `transform` closure.
183+
///
184+
/// - Complexity: O(*n*), where *n* is the length of the collection.
185+
@inlinable
186+
public func partitionMap<A, B, C>(
187+
_ transform: (Element) throws -> PartitionMapResult3<A, B, C>
188+
) rethrows -> ([A], [B], [C]) {
189+
var groupA: [A] = []
190+
var groupB: [B] = []
191+
var groupC: [C] = []
192+
193+
for element in self {
194+
switch try transform(element).oneOf {
195+
case .first(let a): groupA.append(a)
196+
case .second(let b): groupB.append(b)
197+
case .third(let c): groupC.append(c)
198+
}
199+
}
200+
201+
return (groupA, groupB, groupC)
202+
}
203+
}

0 commit comments

Comments
 (0)