9
9
//
10
10
//===----------------------------------------------------------------------===//
11
11
12
- public struct LazyChunked < Base: Collection > {
12
+ public struct LazyChunked < Base: Collection , Subject > {
13
13
/// The collection that this instance provides a view onto.
14
14
public let base : Base
15
15
16
16
/// The projection function.
17
17
@usableFromInline
18
- internal var belongInSameGroup : ( Base . Element , Base . Element ) -> Bool
18
+ internal let projection : ( Base . Element ) -> Subject
19
19
20
+ /// The predicate.
20
21
@usableFromInline
21
- internal init ( base: Base , belongInSameGroup: @escaping ( Base . Element , Base . Element ) -> Bool ) {
22
+ internal let belongInSameGroup : ( Subject , Subject ) -> Bool
23
+
24
+ @usableFromInline
25
+ internal init (
26
+ base: Base ,
27
+ projection: @escaping ( Base . Element ) -> Subject ,
28
+ belongInSameGroup: @escaping ( Subject , Subject ) -> Bool
29
+ ) {
22
30
self . base = base
31
+ self . projection = projection
23
32
self . belongInSameGroup = belongInSameGroup
24
33
}
25
34
}
@@ -64,13 +73,14 @@ extension LazyChunked: LazyCollectionProtocol {
64
73
}
65
74
}
66
75
67
- /// Returns the index in the base collection for the first element that
68
- /// doesn't match the current chunk .
76
+ /// Returns the index in the base collection of the end of the chunk starting
77
+ /// at the given index .
69
78
@usableFromInline
70
- internal func endOfChunk( from i: Base . Index ) -> Base . Index {
71
- guard i != base. endIndex else { return base. endIndex }
72
- return base [ base. index ( after: i) ... ]
73
- . firstIndex ( where: { !belongInSameGroup( $0, base [ i] ) } ) ?? base. endIndex
79
+ internal func endOfChunk( startingAt start: Base . Index ) -> Base . Index {
80
+ let subject = projection ( base [ start] )
81
+ return base [ base. index ( after: start) ... ]
82
+ . firstIndex ( where: { !belongInSameGroup( subject, projection ( $0) ) } )
83
+ ?? base. endIndex
74
84
}
75
85
76
86
@inlinable
@@ -80,20 +90,22 @@ extension LazyChunked: LazyCollectionProtocol {
80
90
81
91
@inlinable
82
92
public var endIndex : Index {
83
- Index ( lowerBound: base. endIndex, upperBound : base . endIndex )
93
+ Index ( lowerBound: base. endIndex)
84
94
}
85
95
86
96
@inlinable
87
97
public func index( after i: Index ) -> Index {
88
- let upperBound = i. upperBound ?? endOfChunk ( from: i. lowerBound)
89
- let end = endOfChunk ( from: upperBound)
98
+ precondition ( i != endIndex, " Can't advance past endIndex " )
99
+ let upperBound = i. upperBound ?? endOfChunk ( startingAt: i. lowerBound)
100
+ guard upperBound != base. endIndex else { return endIndex }
101
+ let end = endOfChunk ( startingAt: upperBound)
90
102
return Index ( lowerBound: upperBound, upperBound: end)
91
103
}
92
104
93
105
@inlinable
94
106
public subscript( position: Index ) -> Base . SubSequence {
95
107
let upperBound = position. upperBound
96
- ?? endOfChunk ( from : position. lowerBound)
108
+ ?? endOfChunk ( startingAt : position. lowerBound)
97
109
return base [ position. lowerBound..< upperBound]
98
110
}
99
111
}
@@ -103,17 +115,19 @@ extension LazyChunked.Index: Hashable where Base.Index: Hashable {}
103
115
extension LazyChunked : BidirectionalCollection
104
116
where Base: BidirectionalCollection
105
117
{
106
- /// Returns the index in the base collection for the element that starts
107
- /// the chunk ending at the given index.
118
+ /// Returns the index in the base collection of the start of the chunk ending
119
+ /// at the given index.
108
120
@usableFromInline
109
121
internal func startOfChunk( endingAt end: Base . Index ) -> Base . Index {
122
+ let indexBeforeEnd = base. index ( before: end)
123
+
110
124
// Get the projected value of the last element in the range ending at `end`.
111
- let lastOfPreviousChunk = base [ base . index ( before : end ) ]
125
+ let subject = projection ( base [ indexBeforeEnd ] )
112
126
113
127
// Search backward from `end` for the first element whose projection isn't
114
- // equal to `lastOfPreviousChunk `.
115
- if let firstMismatch = base [ ..< end ]
116
- . lastIndex ( where: { !belongInSameGroup( $0 , lastOfPreviousChunk ) } )
128
+ // equal to `subject `.
129
+ if let firstMismatch = base [ ..< indexBeforeEnd ]
130
+ . lastIndex ( where: { !belongInSameGroup( projection ( $0 ) , subject ) } )
117
131
{
118
132
// If we found one, that's the last element of the _next_ previous chunk,
119
133
// and therefore one position _before_ the start of this chunk.
@@ -127,6 +141,7 @@ extension LazyChunked: BidirectionalCollection
127
141
128
142
@inlinable
129
143
public func index( before i: Index ) -> Index {
144
+ precondition ( i != startIndex, " Can't advance before startIndex " )
130
145
let start = startOfChunk ( endingAt: i. lowerBound)
131
146
return Index ( lowerBound: start, upperBound: i. lowerBound)
132
147
}
@@ -146,8 +161,11 @@ extension LazyCollectionProtocol {
146
161
@inlinable
147
162
public func chunked(
148
163
by belongInSameGroup: @escaping ( Element , Element ) -> Bool
149
- ) -> LazyChunked < Elements > {
150
- LazyChunked ( base: elements, belongInSameGroup: belongInSameGroup)
164
+ ) -> LazyChunked < Elements , Element > {
165
+ LazyChunked (
166
+ base: elements,
167
+ projection: { $0 } ,
168
+ belongInSameGroup: belongInSameGroup)
151
169
}
152
170
153
171
/// Returns a lazy collection of subsequences of this collection, chunked by
@@ -159,10 +177,11 @@ extension LazyCollectionProtocol {
159
177
@inlinable
160
178
public func chunked< Subject: Equatable > (
161
179
on projection: @escaping ( Element ) -> Subject
162
- ) -> LazyChunked < Elements > {
180
+ ) -> LazyChunked < Elements , Subject > {
163
181
LazyChunked (
164
182
base: elements,
165
- belongInSameGroup: { projection ( $0) == projection ( $1) } )
183
+ projection: projection,
184
+ belongInSameGroup: == )
166
185
}
167
186
}
168
187
@@ -172,27 +191,28 @@ extension LazyCollectionProtocol {
172
191
173
192
extension Collection {
174
193
/// Returns a collection of subsequences of this collection, chunked by
175
- /// the given predicate.
194
+ /// grouping elements that project to the same value according to the given
195
+ /// predicate.
176
196
///
177
197
/// - Complexity: O(*n*), where *n* is the length of this collection.
178
- @inlinable
179
- public func chunked(
180
- by belongInSameGroup: ( Element , Element ) -> Bool
181
- ) -> [ SubSequence ] {
198
+ @usableFromInline
199
+ internal func chunked< Subject> (
200
+ on projection: ( Element ) throws -> Subject ,
201
+ by belongInSameGroup: ( Subject , Subject ) throws -> Bool
202
+ ) rethrows -> [ SubSequence ] {
182
203
guard !isEmpty else { return [ ] }
183
204
var result : [ SubSequence ] = [ ]
184
205
185
206
var start = startIndex
186
- var current = startIndex
187
- while true {
188
- let next = index ( after : current )
189
- if next == endIndex { break }
190
-
191
- if !belongInSameGroup ( self [ current ] , self [ next ] ) {
192
- result . append ( self [ start..< next ] )
193
- start = next
207
+ var subject = try projection ( self [ start ] )
208
+
209
+ for (index , element ) in indexed ( ) . dropFirst ( ) {
210
+ let nextSubject = try projection ( element )
211
+ if try ! belongInSameGroup ( subject , nextSubject ) {
212
+ result . append ( self [ start ..< index ] )
213
+ start = index
214
+ subject = nextSubject
194
215
}
195
- current = next
196
216
}
197
217
198
218
if start != endIndex {
@@ -201,16 +221,26 @@ extension Collection {
201
221
202
222
return result
203
223
}
224
+
225
+ /// Returns a collection of subsequences of this collection, chunked by
226
+ /// the given predicate.
227
+ ///
228
+ /// - Complexity: O(*n*), where *n* is the length of this collection.
229
+ @inlinable
230
+ public func chunked(
231
+ by belongInSameGroup: ( Element , Element ) throws -> Bool
232
+ ) rethrows -> [ SubSequence ] {
233
+ try chunked ( on: { $0 } , by: belongInSameGroup)
234
+ }
204
235
205
236
/// Returns a collection of subsequences of this collection, chunked by
206
237
/// grouping elements that project to the same value.
207
238
///
208
239
/// - Complexity: O(*n*), where *n* is the length of this collection.
209
240
@inlinable
210
241
public func chunked< Subject: Equatable > (
211
- on projection: ( Element ) -> Subject
212
- ) -> [ SubSequence ] {
213
- chunked ( by : { projection ( $0 ) == projection ( $1 ) } )
242
+ on projection: ( Element ) throws -> Subject
243
+ ) rethrows -> [ SubSequence ] {
244
+ try chunked ( on : projection, by : == )
214
245
}
215
246
}
216
-
0 commit comments