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