-
Notifications
You must be signed in to change notification settings - Fork 7
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
Fixed and tidied combinatorics.list_permutation
function
#9
Conversation
…r mistakenly shortens lists with duplicates
Hey Daniel thanks for this! I've converted the PR to draft, please can you address the todos (tests and docs). I'll defer to @NicklasXYZ on whether this change is sensible but it seems reasonable to me. We could also consider making the behaviour configurable with a second parameter: combinatorics.list_permutation([1, 1, 2], ignore_duplicates: False)
// [1, 1, 2]
// [1, 2, 1]
// [1, 1, 2]
// [1, 2, 1]
// [2, 1, 1]
// [2, 1, 1]
combinatorics.list_permutation([1, 1, 2], ignore_duplicates: True)
// [1, 1, 2]
// [1, 2, 1]
// [2, 1, 1] This might make the implementation a bit clunkier but it would definitely be more performant than suggesting folks iterate the list again with
If a more efficient version is fairly simple to implement I think we should favour it over readability: perf wins for number-crunching stuff is likely to be quite high impact! |
Might be good to just leave this as a comment above the assertion too so folks in the future know this assertion is safe! |
I am sorry for keeping you waiting, I will get to this very soon 😅 |
Thank you @DanielSherlock, thank you for fixing this and @hayleigh-dot-dev for already having a look. I think behavior A that you are describing is a reasonable default choice to go with, but in the future it could be cool to have both behaviors implemented as @hayleigh-dot-dev also points out. If you could update the tests and documentation I'd be happy to approve this. With respect to the performance of the implementations, of course if things can be improved in that department as well, I would not turn down such improvements ;) |
No worries, I'm not in any rush (and I hope you aren't too)! Actually, I thought after posting this pull request: Oh before I start writing tests and docs I should go and look at the standard (or standard-ish) libraries of other languages to see how they handle this case. Then I got distracted. And now it is now. As far as I can tell, it's like this:
(I had hoped to find a list-permuting function in the standard (or standard-ish) libraries of Erlang or Elixir, and prioritise similarity to that. But no luck, not even Elixir's Personally, I like the way Swift handles it. Plus, their documentation is clear (with examples) and also has some hints for possible efficient implementations. The Clojure implementation seem nice and clean too, and might be easy to port. Not all other documentation was similarly clear... Another thing to note: Almost all of the languages I surveyed returned an iterator (or equivalent thing in that language), even when given a list/array/etc. If we are going to be worried about performance, we might consider doing the same. This after all exactly the sort of function where iterators make a lot of sense:
|
I think we could discuss returning an iterator in a separate PR. It'd be a breaking change and just generally beyond the scope of what's already here. Better to merge incremental improvements to the library than spend a lot of back-and-forth making a single PR cover everything all at once! |
Thanks a lot for the Table Daniel. It's a really nice overview and you are right. Returning an iterator would be the way to go for sure :) |
Very practical suggestions, thanks. I've added tests and docs and an explanatory comment to the current implementation.
When running this, I do get loads of warnings - it seems from Gleam's recent type import syntax change. I've not gone and fixed all of those in this PR. I'm happy to create separate issues for:
|
Looks good to me. I'd be happy, if you could add the other issues :)! |
Hi, hope this is okay.
The problem
I noticed an error in the implementation of the
combinatorics.list_permutation
function, when provided with lists containing duplicate items. For example (printing each permutation on a new line):Clearly, none of those are permutations of [1, 1, 2]! It did work correctly when given lists with unique elements:
The issue was the use of
filter
on line 357. This removed from the remaining list all duplicates ofx
, when it shouldn't have.New behaviour
When fixing this, I considered two reasonable behaviours for list_permutations:
factorial(list.length(whichever_list))
.Both behaviours are plausibly useful. I have decided, for this pull request, to implement behaviour A. Partly because it was a simpler change to write, but also because I think it is slightly more versatile: if you need behaviour B it is as simple as calling the function with behaviour A, and then piping the result to
list.unique
:To implement this new behaviour, I switched from
filter
topop
. This always removes exactly one instance ofx
, leaving its duplicates in the remainder of the list. One difficulty is thatpop
returns a result in case the element it's searching for isn't in the list. But it always will be (asx
is drawn from the list in the line above), so we can be sure to get anOk(...)
and I use alet assert
expression to destructure this.This is not exactly the most efficient way to write this function, but it has the same performance as what was there before, and is fairly readable. I would be happy to write a more efficient version if needed.
Other changes
I made a few other code changes at the same time. These do not have any effect on the behaviour of the function.
flat_map
andconcat
for the implementation oflist_permutation
(and nothing else). As these are standard library functions (in the list module, which we already import) we don't need to duplicate them locally, so I got rid of them.flat_map
on line 354 to useuse
. Personally, I find this more readable.To do
I have not yet: