@@ -246,3 +246,177 @@ extension Collection {
246
246
try chunked ( on: projection, by: == )
247
247
}
248
248
}
249
+
250
+ //===----------------------------------------------------------------------===//
251
+ // chunks(ofCount:)
252
+ //===----------------------------------------------------------------------===//
253
+
254
+ /// A collection that presents the elements of its base collection
255
+ /// in `SubSequence` chunks of any given count.
256
+ ///
257
+ /// A `ChunkedByCount` is a lazy view on the base Collection, but it does not implicitly confer
258
+ /// laziness on algorithms applied to its result. In other words, for ordinary collections `c`:
259
+ ///
260
+ /// * `c.chunks(ofCount: 3)` does not create new storage
261
+ /// * `c.chunks(ofCount: 3).map(f)` maps eagerly and returns a new array
262
+ /// * `c.lazy.chunks(ofCount: 3).map(f)` maps lazily and returns a `LazyMapCollection`
263
+ public struct ChunkedByCount < Base: Collection > {
264
+
265
+ public typealias Element = Base . SubSequence
266
+
267
+ @usableFromInline
268
+ internal let base : Base
269
+
270
+ @usableFromInline
271
+ internal let chunkCount : Int
272
+
273
+ @usableFromInline
274
+ internal var computedStartIndex : Index
275
+
276
+ /// Creates a view instance that presents the elements of `base`
277
+ /// in `SubSequence` chunks of the given count.
278
+ ///
279
+ /// - Complexity: O(n)
280
+ @inlinable
281
+ internal init ( _base: Base , _chunkCount: Int ) {
282
+ self . base = _base
283
+ self . chunkCount = _chunkCount
284
+
285
+ // Compute the start index upfront in order to make
286
+ // start index a O(1) lookup.
287
+ let baseEnd = _base. index (
288
+ _base. startIndex, offsetBy: _chunkCount,
289
+ limitedBy: _base. endIndex
290
+ ) ?? _base. endIndex
291
+
292
+ self . computedStartIndex =
293
+ Index ( _baseRange: _base. startIndex..< baseEnd)
294
+ }
295
+ }
296
+
297
+ extension ChunkedByCount : Collection {
298
+ public struct Index {
299
+ @usableFromInline
300
+ internal let baseRange : Range < Base . Index >
301
+
302
+ @usableFromInline
303
+ internal init ( _baseRange: Range < Base . Index > ) {
304
+ self . baseRange = _baseRange
305
+ }
306
+ }
307
+
308
+ /// - Complexity: O(n)
309
+ public var startIndex : Index { computedStartIndex }
310
+ public var endIndex : Index {
311
+ Index ( _baseRange: base. endIndex..< base. endIndex)
312
+ }
313
+
314
+ /// - Complexity: O(n)
315
+ public subscript( i: Index ) -> Element {
316
+ base [ i. baseRange]
317
+ }
318
+
319
+ @inlinable
320
+ public func index( after i: Index ) -> Index {
321
+ let baseIdx = base. index (
322
+ i. baseRange. upperBound, offsetBy: chunkCount,
323
+ limitedBy: base. endIndex
324
+ ) ?? base. endIndex
325
+ return Index ( _baseRange: i. baseRange. upperBound..< baseIdx)
326
+ }
327
+ }
328
+
329
+ extension ChunkedByCount . Index : Comparable {
330
+ @inlinable
331
+ public static func < ( lhs: ChunkedByCount . Index ,
332
+ rhs: ChunkedByCount . Index ) -> Bool {
333
+ lhs. baseRange. lowerBound < rhs. baseRange. lowerBound
334
+ }
335
+ }
336
+
337
+ extension ChunkedByCount :
338
+ BidirectionalCollection , RandomAccessCollection
339
+ where Base: RandomAccessCollection {
340
+ @inlinable
341
+ public func index( before i: Index ) -> Index {
342
+ var offset = chunkCount
343
+ if i. baseRange. lowerBound == base. endIndex {
344
+ let remainder = base. count% chunkCount
345
+ if remainder != 0 {
346
+ offset = remainder
347
+ }
348
+ }
349
+
350
+ let baseIdx = base. index (
351
+ i. baseRange. lowerBound, offsetBy: - offset,
352
+ limitedBy: base. startIndex
353
+ ) ?? base. startIndex
354
+ return Index ( _baseRange: baseIdx..< i. baseRange. lowerBound)
355
+ }
356
+
357
+ @inlinable
358
+ public func distance( from start: Index , to end: Index ) -> Int {
359
+ let distance =
360
+ base. distance ( from: start. baseRange. lowerBound,
361
+ to: end. baseRange. lowerBound)
362
+ let ( quotient, remainder) =
363
+ distance. quotientAndRemainder ( dividingBy: chunkCount)
364
+ // Increment should account for negative distances.
365
+ if remainder < 0 {
366
+ return quotient - 1
367
+ }
368
+ return quotient + ( remainder == 0 ? 0 : 1 )
369
+ }
370
+
371
+ @inlinable
372
+ public var count : Int {
373
+ let ( quotient, remainder) =
374
+ base. count. quotientAndRemainder ( dividingBy: chunkCount)
375
+ return quotient + ( remainder == 0 ? 0 : 1 )
376
+ }
377
+ }
378
+
379
+ extension Collection {
380
+ /// Returns a `ChunkedCollection<Self>` view presenting the elements
381
+ /// in chunks with count of the given count parameter.
382
+ ///
383
+ /// - Parameter size: The size of the chunks. If the count parameter
384
+ /// is evenly divided by the count of the base `Collection` all the
385
+ /// chunks will have the count equals to size.
386
+ /// Otherwise, the last chunk will contain the remaining elements.
387
+ ///
388
+ /// let c = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
389
+ /// print(c.chunks(ofCount: 5).map(Array.init))
390
+ /// // [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
391
+ ///
392
+ /// print(c.chunks(ofCount: 3).map(Array.init))
393
+ /// // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
394
+ ///
395
+ /// - Complexity: O(1)
396
+ @inlinable
397
+ public func chunks( ofCount count: Int ) -> ChunkedByCount < Self > {
398
+ precondition ( count > 0 , " Cannot chunk with count <= 0! " )
399
+ return ChunkedByCount ( _base: self , _chunkCount: count)
400
+ }
401
+ }
402
+
403
+ // Conditional conformances.
404
+ extension ChunkedByCount : Equatable where Base: Equatable { }
405
+
406
+ // Since we have another stored property of type `Index` on the
407
+ // collection, synthetization of hashble conformace would require
408
+ // a `Base.Index: Hashable` constraint, so we implement the hasher
409
+ // only in terms of base. Since the computed index is based on it,
410
+ // it should make a difference here.
411
+ extension ChunkedByCount : Hashable where Base: Hashable {
412
+ public func hash( into hasher: inout Hasher ) {
413
+ hasher. combine ( base)
414
+ }
415
+ }
416
+ extension ChunkedByCount . Index : Hashable where Base. Index: Hashable { }
417
+
418
+ // Lazy conditional conformance.
419
+ extension ChunkedByCount : LazySequenceProtocol
420
+ where Base: LazySequenceProtocol { }
421
+ extension ChunkedByCount : LazyCollectionProtocol
422
+ where Base: LazyCollectionProtocol { }
0 commit comments