All combinations#51
Conversation
natecook1000
left a comment
There was a problem hiding this comment.
Thanks for this addition, @mdznr! 👏👏
I'm a big fan of the range-based version, especially that you can pass all kinds of range expressions and it just works. The double plural in the name strikes me as a little odd, but I don't think it's worth changing unless there's something clearly better.
I'm not sold on the no-argument version. Can you explain a bit about your use case for including this? As a user, I would be surprised that the result contained single-element "combinations" and very surprised about the leading zero-element value. I'm inclined to just document the usage in the range-based version, since you can use a range expression to explicitly get the behavior you want — e.g. numbers.combinations(ofCounts: 0...).
| : 0 | ||
| return k.map { | ||
| binomial(n: n, k: $0) | ||
| }.reduce(0, +) |
There was a problem hiding this comment.
Future optimization — the binomial coefficient is symmetric around N/2 or (N - 1)/2, so up to half the work could be skipped here. Or there might be a way to calculate this directly?
There was a problem hiding this comment.
Good call. I’ll play around with a way to do this that maintains readability. We can probably get most of the algorithmic performance benefits by using memoization in binomial.
There was a problem hiding this comment.
I tested adding a small memoization cache (Dictionary) to binomial. To get the count value for Combinations of a Collection of length 26 ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), using count 1..<26 it cut down the number of calls to binomial from 206 to 128.
I have some ideas on how to further improve performance for this. There is already a special case when the range of k is 0...base.count to use 1 << n (2^n). However, a potential common case is excluding the empty and/or complete set (1..<base.count), which we can easily calculate using 2^n minus 1 (or 2). We can do some work to get the absolute minimum number of computations necessary to calculate a contiguous range of values in the last row of the arithmetic triangle.
Should I move that to a separate, subsequent PR so not to block this one?
There was a problem hiding this comment.
That can be done later for sure!
e36b638 to
85f126f
Compare
|
Thanks for the feedback, @natecook1000 !
I agree that it does sound a bit odd. I’d definitely be up for changing it if anyone has any suggestions. Perhaps something like
I do see why it seems bit weird at first, but there’s a couple reasons why I have it as such:
|
We might be able to get away with just overloading
I think these are some strong points, however I'm not sure they overcomes (for me at least) just how trivial and explicit it is to compose |
I like that idea. However, while having two functions named
Sounds good 👍. My original use case was to iterate through all combinations, so that’s where I started. Support for arbitrary ranges was added along the way since it was easy to do. |
`k` is `internal` only and unmodified
This mirrors the order of the range
… specifying combination sizes
263c4f4 to
a3ec470
Compare
That's a great idea, Kyle. @mdznr can you double check your error here? I'm not seeing an ambiguity with that change — maybe you had a stale |
|
Also, I've definitely seen requests for |
I was surprised to see that it wasn’t working, too. After relaunching Xcode this morning, it seems to work. I’ll go ahead and make that change now. |
Functions that accept a single integer value still name the parameter as `k`.
|
Does it make sense to add the support for ranges to |
babf7de to
c837120
Compare
I think so. It’ll definitely produce quite a lot of permutations to iterate through, but should be done for parity. If the high-level changes for this PR are considered approved, I’ll begin work on making the changes there, too. |
c837120 to
3ea695b
Compare
kylemacomber
left a comment
There was a problem hiding this comment.
I didn't go over the code and comments with a fine toothed comb, Nate's more qualified to do that, but at a more superficial level this all looks fantastic! Wonderful work @mdznr!
ff356ae to
f3ac86d
Compare
natecook1000
left a comment
There was a problem hiding this comment.
Looking close, @mdznr! 👏
6fc7187 to
345ea04
Compare
|
Thanks for the feedback, @kylemacomber and @natecook1000 . I think I’ve addressed all the feedback so far. Once this is approved/merged, I’ll create the issues (enhancements) to optimize the performance of |
Co-authored-by: Kyle Macomber <kmacomber@apple.com>
This is similar to how `Permutations` works
a19cc17 to
7a6c135
Compare
Adds the ability to easily get permutations of a range of sizes, like #51 did for Combinations.

Description
This addition adds the ability to easily get combinations of a range of sizes, instead of the previous behavior of a single, fixed size,
k.Detailed Design
The
combinations(ofCount:)function that takes aRangeExpressionis added in an extension ofCollection.If the upper bound of the range exceeds the length of the collection, no such combinations of those sizes will be returned, effectively clamping the range to
0...nwherenis the length of the collection. The behavior of negative numbers also remains unchanged (asserting).This also updates the implementation of the existing
combinations(ofCount:)function to initialize aCombinationswith a range ofk...kto provide the same functionality as it did before.Complexity
The complexity remains O(1) for initializing
Combinations. Accessing the next combination also remains O(1).Naming
The name
combinations(ofCount:)is the same as the original, but reflects the ability to provide multiple counts. Alternatively consideredcombinations(ofCounts:)andcombinations(ofCountsIn range:).Documentation Plan
The documentation in
Guides/Combinations.mdhas been updated to reflect the new function. Inline code documentation also exists for each of the functions.Test Plan
countproperty, which was previously untestedSource Impact
The functionality is purely additive. The behavior of the existing function is unchanged.
Checklist