-
Notifications
You must be signed in to change notification settings - Fork 446
All combinations #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
All combinations #51
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can be done later for sure!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 <[email protected]>
This is similar to how `Permutations` works
a19cc17
to
7a6c135
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
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 aRangeExpression
is 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...n
wheren
is 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 aCombinations
with a range ofk...k
to 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.md
has been updated to reflect the new function. Inline code documentation also exists for each of the functions.Test Plan
count
property, which was previously untestedSource Impact
The functionality is purely additive. The behavior of the existing function is unchanged.
Checklist