@@ -143,6 +143,238 @@ extension LazyChunked: BidirectionalCollection
143
143
}
144
144
}
145
145
146
+ /// A collection wrapper that evenly breaks a collection into a given number of
147
+ /// chunks.
148
+ public struct EvenChunks < Base: Collection > {
149
+ /// The base collection.
150
+ @usableFromInline
151
+ internal let base : Base
152
+
153
+ /// The number of equal chunks the base collection is divided into.
154
+ @usableFromInline
155
+ internal let numberOfChunks : Int
156
+
157
+ /// The count of the base collection.
158
+ @usableFromInline
159
+ internal let baseCount : Int
160
+
161
+ /// The upper bound of the first chunk.
162
+ @usableFromInline
163
+ internal var firstUpperBound : Base . Index
164
+
165
+ @usableFromInline
166
+ internal init (
167
+ base: Base ,
168
+ numberOfChunks: Int ,
169
+ baseCount: Int ,
170
+ firstUpperBound: Base . Index
171
+ ) {
172
+ self . base = base
173
+ self . numberOfChunks = numberOfChunks
174
+ self . baseCount = base. count
175
+ self . firstUpperBound = firstUpperBound
176
+ }
177
+
178
+ @usableFromInline
179
+ internal init ( base: Base , numberOfChunks: Int ) {
180
+ self . base = base
181
+ self . numberOfChunks = numberOfChunks
182
+ self . baseCount = base. count
183
+ self . firstUpperBound = base. startIndex
184
+
185
+ if numberOfChunks > 0 {
186
+ firstUpperBound = endOfChunk ( startingAt: base. startIndex, offset: 0 )
187
+ }
188
+ }
189
+ }
190
+
191
+ extension EvenChunks {
192
+ /// Returns the number of chunks with size `smallChunkSize + 1` at the start
193
+ /// of this collection.
194
+ @usableFromInline
195
+ internal var numberOfLargeChunks : Int {
196
+ baseCount % numberOfChunks
197
+ }
198
+
199
+ /// Returns the size of the small chunks at the end of this collection.
200
+ @usableFromInline
201
+ internal var smallChunkSize : Int {
202
+ baseCount / numberOfChunks
203
+ }
204
+
205
+ /// Returns the size of a chunk at a given offset.
206
+ @usableFromInline
207
+ internal func sizeOfChunk( offset: Int ) -> Int {
208
+ let isLargeChunk = offset < numberOfLargeChunks
209
+ return baseCount / numberOfChunks + ( isLargeChunk ? 1 : 0 )
210
+ }
211
+
212
+ /// Returns the index in the base collection of the end of the chunk starting
213
+ /// at the given index.
214
+ @usableFromInline
215
+ internal func endOfChunk( startingAt start: Base . Index , offset: Int ) -> Base . Index {
216
+ base. index ( start, offsetBy: sizeOfChunk ( offset: offset) )
217
+ }
218
+
219
+ /// Returns the index in the base collection of the start of the chunk ending
220
+ /// at the given index.
221
+ @usableFromInline
222
+ internal func startOfChunk( endingAt end: Base . Index , offset: Int ) -> Base . Index {
223
+ base. index ( end, offsetBy: - sizeOfChunk( offset: offset) )
224
+ }
225
+
226
+ /// Returns the index that corresponds to the chunk that starts at the given
227
+ /// base index.
228
+ @usableFromInline
229
+ internal func indexOfChunk( startingAt start: Base . Index , offset: Int ) -> Index {
230
+ guard offset != numberOfChunks else { return endIndex }
231
+ let end = endOfChunk ( startingAt: start, offset: offset)
232
+ return Index ( start..< end, offset: offset)
233
+ }
234
+
235
+ /// Returns the index that corresponds to the chunk that ends at the given
236
+ /// base index.
237
+ @usableFromInline
238
+ internal func indexOfChunk( endingAt end: Base . Index , offset: Int ) -> Index {
239
+ let start = startOfChunk ( endingAt: end, offset: offset)
240
+ return Index ( start..< end, offset: offset)
241
+ }
242
+ }
243
+
244
+ public struct EvenChunksIndex < Base: Comparable > : Comparable {
245
+ /// The range corresponding to the chunk at this position.
246
+ @usableFromInline
247
+ internal var baseRange : Range < Base >
248
+
249
+ /// The offset corresponding to the chunk at this position. The first chunk
250
+ /// has offset `0` and all other chunks have an offset `1` greater than the
251
+ /// previous.
252
+ @usableFromInline
253
+ internal var offset : Int
254
+
255
+ @usableFromInline
256
+ internal init ( _ baseRange: Range < Base > , offset: Int ) {
257
+ self . baseRange = baseRange
258
+ self . offset = offset
259
+ }
260
+
261
+ @inlinable
262
+ public static func == ( lhs: Self , rhs: Self ) -> Bool {
263
+ lhs. offset == rhs. offset
264
+ }
265
+
266
+ @inlinable
267
+ public static func < ( lhs: Self , rhs: Self ) -> Bool {
268
+ lhs. offset < rhs. offset
269
+ }
270
+ }
271
+
272
+ extension EvenChunks : Collection {
273
+ public typealias Element = Base . SubSequence
274
+ public typealias Index = EvenChunksIndex < Base . Index >
275
+ public typealias SubSequence = EvenChunks < Base . SubSequence >
276
+
277
+ @inlinable
278
+ public var startIndex : Index {
279
+ Index ( base. startIndex..< firstUpperBound, offset: 0 )
280
+ }
281
+
282
+ @inlinable
283
+ public var endIndex : Index {
284
+ Index ( base. endIndex..< base. endIndex, offset: numberOfChunks)
285
+ }
286
+
287
+ @inlinable
288
+ public func index( after i: Index ) -> Index {
289
+ precondition ( i != endIndex, " Can't advance past endIndex " )
290
+ let start = i. baseRange. upperBound
291
+ return indexOfChunk ( startingAt: start, offset: i. offset + 1 )
292
+ }
293
+
294
+ @inlinable
295
+ public subscript( position: Index ) -> Element {
296
+ precondition ( position != endIndex)
297
+ return base [ position. baseRange]
298
+ }
299
+
300
+ @inlinable
301
+ public subscript( bounds: Range < Index > ) -> SubSequence {
302
+ func baseCount( before index: Index ) -> Int {
303
+ let smallChunkSize = self . baseCount / numberOfChunks
304
+ let numberOfLargeChunks = Swift . min ( index. offset, self . numberOfLargeChunks)
305
+ return index. offset * smallChunkSize + numberOfLargeChunks
306
+ }
307
+
308
+ return . init(
309
+ base: base [ bounds. lowerBound. baseRange. lowerBound..< bounds. upperBound. baseRange. lowerBound] ,
310
+ numberOfChunks: bounds. upperBound. offset - bounds. lowerBound. offset,
311
+ baseCount: baseCount ( before: bounds. upperBound) - baseCount( before: bounds. lowerBound) ,
312
+ firstUpperBound: bounds. lowerBound. baseRange. upperBound
313
+ )
314
+ }
315
+
316
+ @inlinable
317
+ public func index( _ i: Index , offsetBy distance: Int ) -> Index {
318
+ /// Returns the base distance between two `EvenChunks` indices from the end
319
+ /// of one to the start of the other, when given their offsets.
320
+ func baseDistance( from offsetA: Int , to offsetB: Int ) -> Int {
321
+ let smallChunkSize = baseCount / numberOfChunks
322
+ let numberOfChunks = ( offsetB - offsetA) - 1
323
+
324
+ let largeChunksEnd = Swift . min ( self . numberOfLargeChunks, offsetB)
325
+ let largeChunksStart = Swift . min ( self . numberOfLargeChunks, offsetA + 1 )
326
+ let numberOfLargeChunks = largeChunksEnd - largeChunksStart
327
+
328
+ return smallChunkSize * numberOfChunks + numberOfLargeChunks
329
+ }
330
+
331
+ if distance == 0 {
332
+ return i
333
+ } else if distance > 0 {
334
+ let offset = i. offset + distance
335
+ let baseOffset = baseDistance ( from: i. offset, to: offset)
336
+ let start = base. index ( i. baseRange. upperBound, offsetBy: baseOffset)
337
+ return indexOfChunk ( startingAt: start, offset: offset)
338
+ } else {
339
+ let offset = i. offset + distance
340
+ let baseOffset = baseDistance ( from: offset, to: i. offset)
341
+ let end = base. index ( i. baseRange. lowerBound, offsetBy: - baseOffset)
342
+ return indexOfChunk ( endingAt: end, offset: offset)
343
+ }
344
+ }
345
+
346
+ @inlinable
347
+ public func index( _ i: Index , offsetBy distance: Int , limitedBy limit: Index ) -> Index ? {
348
+ if distance >= 0 {
349
+ if ( 0 ..< distance) . contains ( self . distance ( from: i, to: limit) ) {
350
+ return nil
351
+ }
352
+ } else {
353
+ if ( 0 ..< ( - distance) ) . contains ( self . distance ( from: limit, to: i) ) {
354
+ return nil
355
+ }
356
+ }
357
+ return index ( i, offsetBy: distance)
358
+ }
359
+
360
+ @inlinable
361
+ public func distance( from start: Index , to end: Index ) -> Int {
362
+ end. offset - start. offset
363
+ }
364
+ }
365
+
366
+ extension EvenChunksIndex : Hashable where Base: Hashable { }
367
+
368
+ extension EvenChunks : BidirectionalCollection
369
+ where Base: BidirectionalCollection
370
+ {
371
+ @inlinable
372
+ public func index( before i: Index ) -> Index {
373
+ precondition ( i != startIndex, " Can't advance before startIndex " )
374
+ return indexOfChunk ( endingAt: i. baseRange. lowerBound, offset: i. offset - 1 )
375
+ }
376
+ }
377
+
146
378
//===----------------------------------------------------------------------===//
147
379
// lazy.chunked(by:)
148
380
//===----------------------------------------------------------------------===//
@@ -541,3 +773,20 @@ extension ChunkedByCount: LazySequenceProtocol
541
773
where Base: LazySequenceProtocol { }
542
774
extension ChunkedByCount : LazyCollectionProtocol
543
775
where Base: LazyCollectionProtocol { }
776
+
777
+ //===----------------------------------------------------------------------===//
778
+ // evenChunks(count:)
779
+ //===----------------------------------------------------------------------===//
780
+
781
+ extension Collection {
782
+ /// Returns a collection of `count` evenly divided subsequences of this
783
+ /// collection.
784
+ ///
785
+ /// - Complexity: TODO
786
+ @inlinable
787
+ public func evenChunks( count: Int ) -> EvenChunks < Self > {
788
+ precondition ( count >= 0 , " Can't divide into a negative number of chunks " )
789
+ precondition ( count > 0 || isEmpty, " Can't divide a non-empty collection into 0 chunks " )
790
+ return EvenChunks ( base: self , numberOfChunks: count)
791
+ }
792
+ }
0 commit comments