Skip to content
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

Count combinations with replacement #737

Conversation

Philippe-Cholet
Copy link
Member

@Philippe-Cholet Philippe-Cholet commented Aug 26, 2023

@phimuemue
Maybe a comment in remaining_for to suggest to see the big comment in combinations.rs.
I have a test combinations_with_replacement_inexact_size_hints if you want.

Only two differences with combinations *without* replacement:
- this `count` instead of `checked_binomial` ;
- `k` can be bigger than `n`.
For a fast test: `n` and `k` are only up to 7.
@Philippe-Cholet
Copy link
Member Author

@phimuemue What do you think?

Copy link
Member

@phimuemue phimuemue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Philippe-Cholet, thanks again!

Please comment how remaining_for works - I believe that you did your homework there, but I'd really appreciate if you could clear things up for the reader.

Comment on lines 114 to 128
let count = |n: usize, k: usize| checked_binomial((n + k).saturating_sub(1), k);
let k = indices.len();
if first {
count(n, k)
} else {
indices
.iter()
.enumerate()
// TODO: Once the MSRV hits 1.37.0, we can sum options instead:
// .map(|(i, n0)| count(n - 1 - *n0, k - i))
// .sum()
.fold(Some(0), |sum, (i, n0)| {
sum.and_then(|s| s.checked_add(count(n - 1 - *n0, k - i)?))
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you that this is correct, but I have a hard time comprehending this without comments. Is it the "stars and sticks" thing that leads to binomial(n+k-1, k)?

Could you comment the loop similar to what we did in the other cases?

In addition: Could n+k overflow? And if so, shouldn't we return None here instead of panicking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose the expression "stars and bars" from wikipedia, expression I did not know. I added a comment.
Good catch n+k overflowing, handled.
And I copied/updated the previous comment about the loop.

src/combinations_with_replacement.rs Show resolved Hide resolved
src/combinations_with_replacement.rs Show resolved Hide resolved
src/combinations_with_replacement.rs Show resolved Hide resolved
@phimuemue
Copy link
Member

bors r+

Thanks @Philippe-Cholet.

One thing: I'm constantly annoyed by the first field. Do you happen to know if there's a way to avoid this? (No need to work on it, I was just interested if you have insights into that.)

@bors
Copy link
Contributor

bors bot commented Sep 4, 2023

Build succeeded!

The publicly hosted instance of bors-ng is deprecated and will go away soon.

If you want to self-host your own instance, instructions are here.
For more help, visit the forum.

If you want to switch to GitHub's built-in merge queue, visit their help page.

@bors bors bot merged commit c7a038e into rust-itertools:master Sep 4, 2023
9 checks passed
@Philippe-Cholet
Copy link
Member Author

Philippe-Cholet commented Sep 4, 2023

@phimuemue I was thinking it could be indices: Option<Vec<usize>> to remove this field too.
Without replacement, the lazy buffer length would give us k but there is reset for powerset so it would be a real pain I think if we could even make it work.
With replacement, we don't prefill the lazy buffer so its length does not give us k.
So basically to know k without storing it (which is bigger than a boolean), we kinda need indices to be a vector of length k, not an optional one.

And with indices: Vec<usize> and not first we would skip the first combination with the current "next" algo. I suppose we could update the "next" algo: get the next element from current unchanged indices then only then update indices for the next time instead of updating indices first but that's quite some change for such a small benefit. And it would offset what we did here (count remaining elements based on indices) by 1. And when getting the last element, indices should become what: None? (representing the indices for the next element when there is no next element).

Another better alternative I guess would be our own alternative to Option<Vec<usize>> to know k anyway: enum State { Start { k: usize }, Ongoing { indices: Vec<usize> } }. That would be my choice.

PS: I was also thinking about merging Powerset, Combinations and CombinationsWithReplacement but I'm unsure it would be beneficial.

EDIT: Because of reset where we update indices rather than ditch it, the above State enum is not as good as our current first: bool, indices: Vec<usize>.

@Philippe-Cholet Philippe-Cholet deleted the count_combinations_with_replacement branch September 4, 2023 16:08
@jswrenn jswrenn added this to the next milestone Nov 14, 2023
@jswrenn jswrenn mentioned this pull request Nov 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants