swift-algorithms icon indicating copy to clipboard operation
swift-algorithms copied to clipboard

Evenly divide a collection into chunks

Open timvermeulen opened this issue 4 years ago • 5 comments

Divide a collection into a given number of chunks as evenly as possible, with larger chunks at the start.

for chunk in Array(0..<10).evenlyChunked(in: 4) {
    print(chunk)  // [0, 1, 2], [3, 4, 5], [6, 7], [8, 9]
}

EvenChunks<Base>.SubSequence is set to be EvenChunks<Base.SubSequence> which works out rather nicely. Other collections in this package that could benefit from this as well are LazyChunked, ChunkedByCount, and Windows.

Checklist

  • [x] I've added at least one test that validates that my change is working, if appropriate
  • [x] I've followed the code style of the rest of the project
  • [x] I've read the Contribution Guidelines
  • [x] I've updated the documentation if necessary

timvermeulen avatar Mar 11 '21 13:03 timvermeulen

@swift-ci please test

timvermeulen avatar Apr 22 '21 19:04 timvermeulen

I tried for a bit to make EvenChunks<Base>.SubSequence equal to EvenChunks<Base.SubSequence>, but it doesn't really seem to be possible (with how EvenChunks.Index currently works)!

It is important that Index only uses its offset property when testing for equality, because two unequal indices can correspond to the same empty slice of the base collection when the number of chunks exceeds the size of the collection:

print(Array((0..<3).evenlyChunked(into: 5)))
// [0..<1, 1..<2, 2..<3, 3..<3, 3..<3]

At the same time, every collection subsequence needs to share its indices with the collection it came from, in particular:

let chunks = (0..<6).evenlyChunked(into: 2)
let index = chunks.index(after: chunks.startIndex)
let slice = chunks[index...]
print(index == slice.startIndex)  // should print "true"

This is problematic because the slice doesn't know it's a slice, and so it assigns its startIndex an offset of 0, not 1. EvenChunks would need an extra stored property to make this work which is a line I didn't want to cross.

timvermeulen avatar Apr 22 '21 20:04 timvermeulen

@swift-ci please test

timvermeulen avatar Apr 22 '21 20:04 timvermeulen

I think I like evenlyChunked(in: 4) more than evenlyChunked(into: 4), since into has connotations of the argument being passed inout, for example:

  • hash(into:)
  • reduce(into:_:)

kylemacomber avatar Apr 23 '21 18:04 kylemacomber

@swift-ci please test

timvermeulen avatar Apr 23 '21 18:04 timvermeulen

@swift-ci Please test

natecook1000 avatar Jul 21 '23 19:07 natecook1000