Skip to content

Commit 447af6a

Browse files
committed
Integrate review feedback
This integrates all of the feedback from the review thread. Here is a quick summary: - Change the trailing separator behaviour. We are no longer returning a separator before we are forwarding the error - Add a synchronous and asynchronous closure based `interspersed` method. - Support interspersing every n elements
1 parent b339466 commit 447af6a

File tree

2 files changed

+332
-150
lines changed

2 files changed

+332
-150
lines changed

Sources/AsyncAlgorithms/Interspersed/AsyncInterspersedSequence.swift

Lines changed: 217 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -9,108 +9,245 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
extension AsyncSequence {
13-
/// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
14-
/// the given separator between each element.
15-
///
16-
/// Any value of this asynchronous sequence's element type can be used as the separator.
17-
///
18-
/// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
19-
///
20-
/// ```
21-
/// let input = ["A", "B", "C"].async
22-
/// let interspersed = input.interspersed(with: "-")
23-
/// for await element in interspersed {
24-
/// print(element)
25-
/// }
26-
/// // Prints "A" "-" "B" "-" "C"
27-
/// ```
28-
///
29-
/// - Parameter separator: The value to insert in between each of this async
30-
/// sequence’s elements.
31-
/// - Returns: The interspersed asynchronous sequence of elements.
32-
@inlinable
33-
public func interspersed(with separator: Element) -> AsyncInterspersedSequence<Self> {
34-
AsyncInterspersedSequence(self, separator: separator)
35-
}
12+
public extension AsyncSequence {
13+
/// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
14+
/// the given separator between each element.
15+
///
16+
/// Any value of this asynchronous sequence's element type can be used as the separator.
17+
///
18+
/// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
19+
///
20+
/// ```
21+
/// let input = ["A", "B", "C"].async
22+
/// let interspersed = input.interspersed(with: "-")
23+
/// for await element in interspersed {
24+
/// print(element)
25+
/// }
26+
/// // Prints "A" "-" "B" "-" "C"
27+
/// ```
28+
///
29+
/// - Parameters:
30+
/// - every: Dictates after how many elements a separator should be inserted.
31+
/// - separator: The value to insert in between each of this async sequence’s elements.
32+
/// - Returns: The interspersed asynchronous sequence of elements.
33+
@inlinable
34+
func interspersed(every: Int = 1, with separator: Element) -> AsyncInterspersedSequence<Self> {
35+
AsyncInterspersedSequence(self, every: every, separator: separator)
36+
}
37+
38+
/// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
39+
/// the given separator between each element.
40+
///
41+
/// Any value of this asynchronous sequence's element type can be used as the separator.
42+
///
43+
/// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
44+
///
45+
/// ```
46+
/// let input = ["A", "B", "C"].async
47+
/// let interspersed = input.interspersed(with: "-")
48+
/// for await element in interspersed {
49+
/// print(element)
50+
/// }
51+
/// // Prints "A" "-" "B" "-" "C"
52+
/// ```
53+
///
54+
/// - Parameters:
55+
/// - every: Dictates after how many elements a separator should be inserted.
56+
/// - separator: A closure that produces the value to insert in between each of this async sequence’s elements.
57+
/// - Returns: The interspersed asynchronous sequence of elements.
58+
@inlinable
59+
func interspersed(every: Int = 1, with separator: @Sendable @escaping () -> Element) -> AsyncInterspersedSequence<Self> {
60+
AsyncInterspersedSequence(self, every: every, separator: separator)
61+
}
62+
63+
/// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
64+
/// the given separator between each element.
65+
///
66+
/// Any value of this asynchronous sequence's element type can be used as the separator.
67+
///
68+
/// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
69+
///
70+
/// ```
71+
/// let input = ["A", "B", "C"].async
72+
/// let interspersed = input.interspersed(with: "-")
73+
/// for await element in interspersed {
74+
/// print(element)
75+
/// }
76+
/// // Prints "A" "-" "B" "-" "C"
77+
/// ```
78+
///
79+
/// - Parameters:
80+
/// - every: Dictates after how many elements a separator should be inserted.
81+
/// - separator: A closure that produces the value to insert in between each of this async sequence’s elements.
82+
/// - Returns: The interspersed asynchronous sequence of elements.
83+
@inlinable
84+
func interspersed(every: Int = 1, with separator: @Sendable @escaping () async -> Element) -> AsyncInterspersedSequence<Self> {
85+
AsyncInterspersedSequence(self, every: every, separator: separator)
86+
}
3687
}
3788

3889
/// An asynchronous sequence that presents the elements of a base asynchronous sequence of
3990
/// elements with a separator between each of those elements.
4091
public struct AsyncInterspersedSequence<Base: AsyncSequence> {
41-
@usableFromInline
42-
internal let base: Base
43-
44-
@usableFromInline
45-
internal let separator: Base.Element
46-
47-
@usableFromInline
48-
internal init(_ base: Base, separator: Base.Element) {
49-
self.base = base
50-
self.separator = separator
51-
}
52-
}
92+
@usableFromInline
93+
internal enum Separator {
94+
case element(Element)
95+
case syncClosure(@Sendable () -> Element)
96+
case asyncClosure(@Sendable () async -> Element)
97+
}
5398

54-
extension AsyncInterspersedSequence: AsyncSequence {
55-
public typealias Element = Base.Element
99+
@usableFromInline
100+
internal let base: Base
56101

57-
/// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
58-
public struct Iterator: AsyncIteratorProtocol {
59102
@usableFromInline
60-
internal enum State {
61-
case start
62-
case element(Result<Base.Element, Error>)
63-
case separator
64-
}
103+
internal let separator: Separator
65104

66105
@usableFromInline
67-
internal var iterator: Base.AsyncIterator
106+
internal let every: Int
68107

69108
@usableFromInline
70-
internal let separator: Base.Element
109+
internal init(_ base: Base, every: Int, separator: Element) {
110+
precondition(every > 0, "Separators can only be interspersed ever 1+ elements")
111+
self.base = base
112+
self.separator = .element(separator)
113+
self.every = every
114+
}
71115

72116
@usableFromInline
73-
internal var state = State.start
117+
internal init(_ base: Base, every: Int, separator: @Sendable @escaping () -> Element) {
118+
precondition(every > 0, "Separators can only be interspersed ever 1+ elements")
119+
self.base = base
120+
self.separator = .syncClosure(separator)
121+
self.every = every
122+
}
74123

75124
@usableFromInline
76-
internal init(_ iterator: Base.AsyncIterator, separator: Base.Element) {
77-
self.iterator = iterator
78-
self.separator = separator
125+
internal init(_ base: Base, every: Int, separator: @Sendable @escaping () async -> Element) {
126+
precondition(every > 0, "Separators can only be interspersed ever 1+ elements")
127+
self.base = base
128+
self.separator = .asyncClosure(separator)
129+
self.every = every
79130
}
131+
}
80132

81-
public mutating func next() async rethrows -> Base.Element? {
82-
// After the start, the state flips between element and separator. Before
83-
// returning a separator, a check is made for the next element as a
84-
// separator is only returned between two elements. The next element is
85-
// stored to allow it to be returned in the next iteration. However, if
86-
// the checking the next element throws, the separator is emitted before
87-
// rethrowing that error.
88-
switch state {
89-
case .start:
90-
state = .separator
91-
return try await iterator.next()
92-
case .separator:
93-
do {
94-
guard let next = try await iterator.next() else { return nil }
95-
state = .element(.success(next))
96-
} catch {
97-
state = .element(.failure(error))
98-
}
99-
return separator
100-
case .element(let result):
101-
state = .separator
102-
return try result._rethrowGet()
103-
}
133+
extension AsyncInterspersedSequence: AsyncSequence {
134+
public typealias Element = Base.Element
135+
136+
/// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
137+
public struct Iterator: AsyncIteratorProtocol {
138+
@usableFromInline
139+
internal enum State {
140+
case start(Element?)
141+
case element(Int)
142+
case separator
143+
case finished
144+
}
145+
146+
@usableFromInline
147+
internal var iterator: Base.AsyncIterator
148+
149+
@usableFromInline
150+
internal let separator: Separator
151+
152+
@usableFromInline
153+
internal let every: Int
154+
155+
@usableFromInline
156+
internal var state = State.start(nil)
157+
158+
@usableFromInline
159+
internal init(_ iterator: Base.AsyncIterator, every: Int, separator: Separator) {
160+
self.iterator = iterator
161+
self.separator = separator
162+
self.every = every
163+
}
164+
165+
public mutating func next() async rethrows -> Base.Element? {
166+
// After the start, the state flips between element and separator. Before
167+
// returning a separator, a check is made for the next element as a
168+
// separator is only returned between two elements. The next element is
169+
// stored to allow it to be returned in the next iteration. However, if
170+
// the checking the next element throws, the separator is emitted before
171+
// rethrowing that error.
172+
switch state {
173+
case var .start(element):
174+
do {
175+
if element == nil {
176+
element = try await self.iterator.next()
177+
}
178+
179+
if let element = element {
180+
if every == 1 {
181+
state = .separator
182+
} else {
183+
state = .element(1)
184+
}
185+
return element
186+
} else {
187+
state = .finished
188+
return nil
189+
}
190+
} catch {
191+
state = .finished
192+
throw error
193+
}
194+
195+
case .separator:
196+
do {
197+
if let element = try await iterator.next() {
198+
state = .start(element)
199+
switch separator {
200+
case let .element(element):
201+
return element
202+
203+
case let .syncClosure(closure):
204+
return closure()
205+
206+
case let .asyncClosure(closure):
207+
return await closure()
208+
}
209+
} else {
210+
state = .finished
211+
return nil
212+
}
213+
} catch {
214+
state = .finished
215+
throw error
216+
}
217+
218+
case let .element(count):
219+
do {
220+
if let element = try await iterator.next() {
221+
let newCount = count + 1
222+
if every == newCount {
223+
state = .separator
224+
} else {
225+
state = .element(newCount)
226+
}
227+
return element
228+
} else {
229+
state = .finished
230+
return nil
231+
}
232+
} catch {
233+
state = .finished
234+
throw error
235+
}
236+
237+
case .finished:
238+
return nil
239+
}
240+
}
104241
}
105-
}
106242

107-
@inlinable
108-
public func makeAsyncIterator() -> AsyncInterspersedSequence<Base>.Iterator {
109-
Iterator(base.makeAsyncIterator(), separator: separator)
110-
}
243+
@inlinable
244+
public func makeAsyncIterator() -> AsyncInterspersedSequence<Base>.Iterator {
245+
Iterator(base.makeAsyncIterator(), every: every, separator: separator)
246+
}
111247
}
112248

113-
extension AsyncInterspersedSequence: Sendable where Base: Sendable, Base.Element: Sendable { }
249+
extension AsyncInterspersedSequence: Sendable where Base: Sendable, Base.Element: Sendable {}
250+
extension AsyncInterspersedSequence.Separator: Sendable where Base: Sendable, Base.Element: Sendable {}
114251

115252
@available(*, unavailable)
116253
extension AsyncInterspersedSequence.Iterator: Sendable {}

0 commit comments

Comments
 (0)