@@ -246,3 +246,177 @@ extension Collection {
246246 try chunked ( on: projection, by: == )
247247 }
248248}
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