@@ -198,6 +198,210 @@ extension ChunkedOnCollection: BidirectionalCollection
198
198
199
199
extension ChunkedOnCollection : LazyCollectionProtocol { }
200
200
201
+ /// A collection wrapper that evenly breaks a collection into a given number of
202
+ /// chunks.
203
+ public struct EvenChunksCollection < Base: Collection > {
204
+ /// The base collection.
205
+ @usableFromInline
206
+ internal let base : Base
207
+
208
+ /// The number of equal chunks the base collection is divided into.
209
+ @usableFromInline
210
+ internal let numberOfChunks : Int
211
+
212
+ /// The count of the base collection.
213
+ @usableFromInline
214
+ internal let baseCount : Int
215
+
216
+ /// The upper bound of the first chunk.
217
+ @usableFromInline
218
+ internal var firstUpperBound : Base . Index
219
+
220
+ @inlinable
221
+ internal init ( base: Base , numberOfChunks: Int ) {
222
+ self . base = base
223
+ self . numberOfChunks = numberOfChunks
224
+ self . baseCount = base. count
225
+ self . firstUpperBound = base. startIndex
226
+
227
+ if numberOfChunks > 0 {
228
+ firstUpperBound = endOfChunk ( startingAt: base. startIndex, offset: 0 )
229
+ }
230
+ }
231
+ }
232
+
233
+ extension EvenChunksCollection {
234
+ /// Returns the number of chunks with size `smallChunkSize + 1` at the start
235
+ /// of this collection.
236
+ @inlinable
237
+ internal var numberOfLargeChunks : Int {
238
+ baseCount % numberOfChunks
239
+ }
240
+
241
+ /// Returns the size of a chunk at a given offset.
242
+ @inlinable
243
+ internal func sizeOfChunk( offset: Int ) -> Int {
244
+ let isLargeChunk = offset < numberOfLargeChunks
245
+ return baseCount / numberOfChunks + ( isLargeChunk ? 1 : 0 )
246
+ }
247
+
248
+ /// Returns the index in the base collection of the end of the chunk starting
249
+ /// at the given index.
250
+ @inlinable
251
+ internal func endOfChunk( startingAt start: Base . Index , offset: Int ) -> Base . Index {
252
+ base. index ( start, offsetBy: sizeOfChunk ( offset: offset) )
253
+ }
254
+
255
+ /// Returns the index in the base collection of the start of the chunk ending
256
+ /// at the given index.
257
+ @inlinable
258
+ internal func startOfChunk( endingAt end: Base . Index , offset: Int ) -> Base . Index {
259
+ base. index ( end, offsetBy: - sizeOfChunk( offset: offset) )
260
+ }
261
+
262
+ /// Returns the index that corresponds to the chunk that starts at the given
263
+ /// base index.
264
+ @inlinable
265
+ internal func indexOfChunk( startingAt start: Base . Index , offset: Int ) -> Index {
266
+ guard offset != numberOfChunks else { return endIndex }
267
+ let end = endOfChunk ( startingAt: start, offset: offset)
268
+ return Index ( start..< end, offset: offset)
269
+ }
270
+
271
+ /// Returns the index that corresponds to the chunk that ends at the given
272
+ /// base index.
273
+ @inlinable
274
+ internal func indexOfChunk( endingAt end: Base . Index , offset: Int ) -> Index {
275
+ let start = startOfChunk ( endingAt: end, offset: offset)
276
+ return Index ( start..< end, offset: offset)
277
+ }
278
+ }
279
+
280
+ extension EvenChunksCollection : Collection {
281
+ public struct Index : Comparable {
282
+ /// The range corresponding to the chunk at this position.
283
+ @usableFromInline
284
+ internal var baseRange : Range < Base . Index >
285
+
286
+ /// The offset corresponding to the chunk at this position. The first chunk
287
+ /// has offset `0` and all other chunks have an offset `1` greater than the
288
+ /// previous.
289
+ @usableFromInline
290
+ internal var offset : Int
291
+
292
+ @inlinable
293
+ internal init ( _ baseRange: Range < Base . Index > , offset: Int ) {
294
+ self . baseRange = baseRange
295
+ self . offset = offset
296
+ }
297
+
298
+ @inlinable
299
+ public static func == ( lhs: Self , rhs: Self ) -> Bool {
300
+ lhs. offset == rhs. offset
301
+ }
302
+
303
+ @inlinable
304
+ public static func < ( lhs: Self , rhs: Self ) -> Bool {
305
+ lhs. offset < rhs. offset
306
+ }
307
+ }
308
+
309
+ public typealias Element = Base . SubSequence
310
+
311
+ @inlinable
312
+ public var startIndex : Index {
313
+ Index ( base. startIndex..< firstUpperBound, offset: 0 )
314
+ }
315
+
316
+ @inlinable
317
+ public var endIndex : Index {
318
+ Index ( base. endIndex..< base. endIndex, offset: numberOfChunks)
319
+ }
320
+
321
+ @inlinable
322
+ public func index( after i: Index ) -> Index {
323
+ precondition ( i != endIndex, " Can't advance past endIndex " )
324
+ let start = i. baseRange. upperBound
325
+ return indexOfChunk ( startingAt: start, offset: i. offset + 1 )
326
+ }
327
+
328
+ @inlinable
329
+ public subscript( position: Index ) -> Element {
330
+ precondition ( position != endIndex)
331
+ return base [ position. baseRange]
332
+ }
333
+
334
+ @inlinable
335
+ public func index( _ i: Index , offsetBy distance: Int ) -> Index {
336
+ /// Returns the base distance between two `EvenChunksCollection` indices
337
+ /// from the end of one to the start of the other, when given their offsets.
338
+ func baseDistance( from offsetA: Int , to offsetB: Int ) -> Int {
339
+ let smallChunkSize = baseCount / numberOfChunks
340
+ let numberOfChunks = ( offsetB - offsetA) - 1
341
+
342
+ let largeChunksEnd = Swift . min ( self . numberOfLargeChunks, offsetB)
343
+ let largeChunksStart = Swift . min ( self . numberOfLargeChunks, offsetA + 1 )
344
+ let numberOfLargeChunks = largeChunksEnd - largeChunksStart
345
+
346
+ return smallChunkSize * numberOfChunks + numberOfLargeChunks
347
+ }
348
+
349
+ if distance == 0 {
350
+ return i
351
+ } else if distance > 0 {
352
+ let offset = i. offset + distance
353
+ let baseOffset = baseDistance ( from: i. offset, to: offset)
354
+ let start = base. index ( i. baseRange. upperBound, offsetBy: baseOffset)
355
+ return indexOfChunk ( startingAt: start, offset: offset)
356
+ } else {
357
+ let offset = i. offset + distance
358
+ let baseOffset = baseDistance ( from: offset, to: i. offset)
359
+ let end = base. index ( i. baseRange. lowerBound, offsetBy: - baseOffset)
360
+ return indexOfChunk ( endingAt: end, offset: offset)
361
+ }
362
+ }
363
+
364
+ @inlinable
365
+ public func index( _ i: Index , offsetBy distance: Int , limitedBy limit: Index ) -> Index ? {
366
+ if distance >= 0 {
367
+ if ( 0 ..< distance) . contains ( self . distance ( from: i, to: limit) ) {
368
+ return nil
369
+ }
370
+ } else {
371
+ if ( 0 ..< ( - distance) ) . contains ( self . distance ( from: limit, to: i) ) {
372
+ return nil
373
+ }
374
+ }
375
+ return index ( i, offsetBy: distance)
376
+ }
377
+
378
+ @inlinable
379
+ public func distance( from start: Index , to end: Index ) -> Int {
380
+ end. offset - start. offset
381
+ }
382
+ }
383
+
384
+ extension EvenChunksCollection . Index : Hashable where Base. Index: Hashable { }
385
+
386
+ extension EvenChunksCollection : BidirectionalCollection
387
+ where Base: BidirectionalCollection
388
+ {
389
+ @inlinable
390
+ public func index( before i: Index ) -> Index {
391
+ precondition ( i != startIndex, " Can't advance before startIndex " )
392
+ return indexOfChunk ( endingAt: i. baseRange. lowerBound, offset: i. offset - 1 )
393
+ }
394
+ }
395
+
396
+ extension EvenChunksCollection : RandomAccessCollection
397
+ where Base: RandomAccessCollection { }
398
+
399
+ extension EvenChunksCollection : LazySequenceProtocol
400
+ where Base: LazySequenceProtocol { }
401
+
402
+ extension EvenChunksCollection : LazyCollectionProtocol
403
+ where Base: LazyCollectionProtocol { }
404
+
201
405
//===----------------------------------------------------------------------===//
202
406
// lazy.chunked(by:) / lazy.chunked(on:)
203
407
//===----------------------------------------------------------------------===//
@@ -583,5 +787,40 @@ extension Collection {
583
787
584
788
extension ChunksOfCountCollection . Index : Hashable where Base. Index: Hashable { }
585
789
586
- extension ChunksOfCountCollection : LazySequenceProtocol , LazyCollectionProtocol
790
+ extension ChunksOfCountCollection : LazySequenceProtocol
587
791
where Base: LazySequenceProtocol { }
792
+
793
+ extension ChunksOfCountCollection : LazyCollectionProtocol
794
+ where Base: LazyCollectionProtocol { }
795
+
796
+ //===----------------------------------------------------------------------===//
797
+ // evenlyChunked(in:)
798
+ //===----------------------------------------------------------------------===//
799
+
800
+ extension Collection {
801
+ /// Returns a collection of `count` evenly divided subsequences of this
802
+ /// collection.
803
+ ///
804
+ /// This method divides the collection into a given number of equally sized
805
+ /// chunks. If the length of the collection is not divisible by `count`, the
806
+ /// chunks at the start will be longer than the chunks at the end, like in
807
+ /// this example:
808
+ ///
809
+ /// for chunk in "Hello, world!".evenlyChunked(in: 5) {
810
+ /// print(chunk)
811
+ /// }
812
+ /// // "Hel"
813
+ /// // "lo,"
814
+ /// // " wo"
815
+ /// // "rl"
816
+ /// // "d!"
817
+ ///
818
+ /// - Complexity: O(1) if the collection conforms to `RandomAccessCollection`,
819
+ /// otherwise O(*n*), where *n* is the length of the collection.
820
+ @inlinable
821
+ public func evenlyChunked( in count: Int ) -> EvenChunksCollection < Self > {
822
+ precondition ( count >= 0 , " Can't divide into a negative number of chunks " )
823
+ precondition ( count > 0 || isEmpty, " Can't divide a non-empty collection into 0 chunks " )
824
+ return EvenChunksCollection ( base: self , numberOfChunks: count)
825
+ }
826
+ }
0 commit comments