1. Get all combinations of n digits among m digits
- Posted by jmduro 3 weeks ago
- 340 views
For the last version of my Sudoku Trainer, I needed a function to test all combinations of n digits among m digits (m > n) on all combinations of x cells among y cells (y > x) to find digits restricted to some cells.
Here is the function I wrote to do the job.
include std/convert.e -- remove a digit from a sequence function remove_int(sequence s, integer digit) sequence result = {} for i = 1 to length(s) do if s[i] != digit then result = append(result, s[i]) end if end for return result end function function count_ones(sequence s) integer n = 0 for i = 1 to length(s) do if s[i] = 1 then n += 1 end if end for return n end function function get_combinations(sequence digits, integer n) integer m = length(digits) sequence result= {} for i = 2 to power(2, m) - 1 do sequence s = int_to_bits(i, m) if n = count_ones(s) then result = append(result, remove_int(digits * s, 0)) end if end for return result end function
Here is the test code:
constant TEST_SEQUENCE = {1,2,4,6,7,9} puts(1, "Get all pairs\n") print(1, get_combinations(TEST_SEQUENCE, 2)) puts(1, "\nGet all triplets\n") print(1, get_combinations(TEST_SEQUENCE, 3))
And here is the result:
Get all pairs
{{1,2},{1,4},{2,4},{1,6},{2,6},{4,6},{1,7},{2,7},{4,7},{6,7},{1,9},{2,9},{4,9},{6,9},{7,9}}
Get all triplets
{{1,2,4},{1,2,6},{1,4,6},{2,4,6},{1,2,7},{1,4,7},{2,4,7},{1,6,7},{2,6,7},{4,6,7},{1,2,9},{1,4,9},{2,4,9},{1,6,9},{2,6,9},{4,6,9},{1,7,9},{2,7,9},{4,7,9},{6,7,9}}
Jean-Marc
2. Re: Get all combinations of n digits among m digits
- Posted by petelomax 3 weeks ago
- 336 views
- Last edited 2 weeks ago
Here's the Phix equivalent - yep, it's a builtin, and yep, results are in lexicographic order.
constant test = {1,2,4,6,7,9} ?{"pairs",combinations(test,2)} ?{"triplets",combinations(test,3)} -- {"pairs",{{1,2},{1,4},{1,6},{1,7},{1,9},{2,4},{2,6},{2,7},{2,9},{4,6},{4,7},{4,9},{6,7},{6,9},{7,9}}} -- {"triplets",{{1,2,4},{1,2,6},{1,2,7},{1,2,9},{1,4,6},{1,4,7},{1,4,9},{1,6,7},{1,6,9},{1,7,9},{2,4,6},{2,4,7},{2,4,9},{2,6,7},{2,6,9},{2,7,9},{4,6,7},{4,6,9},{4,7,9},{6,7,9}}}
And here's how I implemented that, in builtins/permute.e, slightly simplified (removing unique, deep_copy and any dependency on Phix's automatic pbr):
global function combinations(sequence s, integer k, integer at=1, sequence part="") if k=0 then -- got a full set return {part} elsif at+k-1<=length(s) then -- get all combinations with and without the next item: return combinations(s,k-1,at+1,append(part,s[at])) &combinations(s,k,at+1,part) end if return {} end function
3. Re: Get all combinations of n digits among m digits
- Posted by jmduro 2 weeks ago
- 310 views
Thank you Pete,
On the following test, the time difference between both methods is high:
atom t0 = time() constant test = {1,2,3,4,5,6,7,8,9} for i = 1 to 10000 do s = combinations(test,2) s = combinations(test,3) s = combinations(test,4) end for printf(1, "duration: %f\n", time() - t0) t0 = time() for i = 1 to 10000 do s = get_combinations(test,2) s = get_combinations(test,3) s = get_combinations(test,4) end for printf(1, "duration: %f\n", time() - t0) maybe_any_key()
duration: 1.741000 duration: 7.538000 Press Any Key to continue...
Here is the OpenEuphoria version:
sequence res = {} -- Phix function public function combinations(sequence s, integer k, integer at=1, sequence res={}, sequence part="") if k=0 then -- got a full set res = append(res,part) elsif at+k-1<=length(s) then -- get all combinations with and without the next item: res = combinations(s,k-1,at+1,res,append(part,s[at])) res = combinations(s,k,at+1,res,part) end if return res end function
Jean-Marc
4. Re: Get all combinations of n digits among m digits
- Posted by petelomax 2 weeks ago
- 304 views
Thank you Pete,
My pleasure. I tweaked it slightly above before you posted, and that version has another marginal gain on Euphoria, from 1.204s to 1.031s (though not Phix, where both [of mine] take 0.89s).
5. Re: Get all combinations of n digits among m digits
- Posted by jmduro 2 weeks ago
- 206 views
I wrote another procedure which is more understandable for me and quite faster.
sequence combis = {} procedure recurse(sequence remaining, /* digits remaining to compute */ integer ne, /* number of digits expected for a valid combination */ sequence selected = {}) /* digits already selected for a potential combination */ integer nr = length(remaining) -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if ns+nr = ne then combis = append(combis, selected & remaining) return end if if nr = 1 or ns = ne then combis = append(combis, selected) return end if -- select next recurse(remove(remaining, 1), ne, selected & remaining[1]) -- skip next recurse(remove(remaining, 1), ne, selected) end procedure
Here are the test results:
Test Speed of all combinations of n digits among m m = 9 n = 2 then 3 then 4 100,000 times Phix:combinations(), duration: 17.857000 s Euphoria:get_combinations, duration: 75.010000 s Euphoria:recurse, duration: 9.478000 s
Jean-Marc
6. Re: Get all combinations of n digits among m digits
- Posted by petelomax 2 weeks ago
- 210 views
Very good, my 15.1s -> your 8.9s -> next's 5.3s -> hopefully last one's 4.9s, not a bad day week's work!
I couldn't understand why you were testing nr=1, if nr=1 and ns!=ne then ?9/0 end if never triggered, so I took it out...
Slicing just the once for both recursive calls saves a fair chunk
sequence combis = {} procedure recurse(sequence remaining, /* digits remaining to compute */ integer ne, /* number of digits expected for a valid combination */ sequence selected = {}) /* digits already selected for a potential combination */ integer nr = length(remaining) -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if ns+nr = ne then combis = append(combis, selected & remaining) elsif ns = ne then combis = append(combis, selected) else object r1 = remaining[1] remaining = remaining[2..$] -- select next recurse(remaining, ne, selected & r1) -- skip next recurse(remaining, ne, selected) end if end procedure
Not slicing at all, except for when "all the rest", saves another 10%
sequence combis = {} procedure recurse(sequence remaining, /* digits remaining to compute ... */ integer needed, /* number of digits expected for a valid combination */ integer done=0, /* ... but do not use remaining[1..done] */ sequence selected = {}) /* digits already selected for a potential combination */ if done=0 then combis = {} end if /* you can and probably will thank me later for that! */ integer nr = length(remaining)-done -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if ns+nr = needed then combis = append(combis, selected & remaining[done+1..$]) elsif ns = needed then combis = append(combis, selected) else -- elsif ns+nr>needed then done += 1 -- select next recurse(remaining, needed, done, selected & remaining[done]) -- skip next recurse(remaining, needed, done, selected) end if end procedure
Slightly miffed that final commented-out test doesn't help and actually slows it down a smidge, but oh well.
Then again, that's the main point here: "all the rest" is not only slightly faster but cuts out a whole heap of pointless "not enough" recursions, I think.
7. Re: Get all combinations of n digits among m digits
- Posted by jmduro 2 weeks ago
- 185 views
Thank you Pete,
I wanted to change my OpenEuphoria tasks on RosettaCode to Euphoria as you suggested but you have been more quick than me. I had just the opportunity to change the last existing one.
Jean-Marc
8. Re: Get all combinations of n digits among m digits
- Posted by jmduro 2 weeks ago
- 188 views
I couldn't understand why you were testing nr=1, if nr=1 and ns!=ne then ?9/0 end if never triggered, so I took it out...
If you don't return at this step if nr = 1, you will try to recurse an empty remaining sequence as I remove the first digit before recursing.
-- select next recurse(remove(remaining, 1), ne, selected & remaining[1]) -- skip next recurse(remove(remaining, 1), ne, selected)
I don't get the same results as yours on your second proposal which takes more time than mine, but your first proposal is quicker.
Mine: Euphoria:recurse, duration: 10.684000 s Yours: Euphoria:recurse, duration: 8.822000 s Euphoria:recurse, duration: 10.956000 s
Jean-Marc
9. Re: Get all combinations of n digits among m digits
- Posted by jmduro 2 weeks ago
- 187 views
Some minor tweaks let save some time:
Euphoria:recurse, duration: 8.850000 s
remaining = remove(remaining, 1)
Euphoria:recurse, duration: 8.700000 s
recurse(remaining, ne, append(selected, r1))
Euphoria:recurse, duration: 8.689000 s
However using built-in functions instead of slicing or operators is not always the best choice: using head(remaining) instead of remaining[1] makes lose time.
Jean-Marc
10. Re: Get all combinations of n digits among m digits
- Posted by petelomax 2 weeks ago
- 165 views
Thank you Pete,
I wanted to change my OpenEuphoria tasks on RosettaCode to Euphoria as you suggested but you have been more quick than me. I had just the opportunity to change the last existing one.
Jean-Marc
You might want to review https://rosettacode.org/wiki/Ackermann_function#Euphoria
11. Re: Get all combinations of n digits among m digits
- Posted by jmduro 2 weeks ago
- 162 views
After trying different optimizations, the best one is this one:
procedure recurse(sequence remaining, /* digits remaining to compute */ integer ne, /* number of digits expected for a valid combination */ sequence selected = {}) /* digits already selected for a potential combination */ integer nr = length(remaining) -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if ns+nr = ne then combis = append(combis, selected & remaining) return elsif ns = ne then combis = append(combis, selected) return else integer r1 = remaining[1] remaining = remove(remaining, 1) -- select next recurse(remaining, ne, append(selected, r1)) -- skip next recurse(remaining, ne, selected) end if end procedure
I even tried to symplify your second proposal as below, but it is not a time saver:
procedure recurse(sequence remaining, /* digits remaining to compute ... */ integer needed, /* number of digits expected for a valid combination */ integer done=1, /* ... but do not use remaining[1..done] */ sequence selected = {}) /* digits already selected for a potential combination */ integer nr = length(remaining)-done+1 -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if ns+nr = needed then combis = append(combis, selected & remaining[done..$]) elsif ns = needed then combis = append(combis, selected) else -- select next recurse(remaining, needed, done+1, selected & remaining[done]) -- skip next recurse(remaining, needed, done+1, selected) end if end procedure
Jean-Marc
12. Re: Get all combinations of n digits among m digits
- Posted by jmduro 1 week ago
- 172 views
Without testing nr=1 I got following error:
C:\Data\Euphoria\v4\Sudoku\tinTrainer\tinTrainer.ex:344 in procedure recurse()
subscript value 1 is out of bounds, reading from a sequence of length 0 - in subscript #1 of '<no-name>'
<no-name> = {}
However, the test was not at the right place. Here is the corrected code.
sequence combis = {} procedure recurse(sequence remaining, /* digits remaining to compute */ integer ne, /* number of digits expected for a valid combination */ sequence selected = {}) /* digits already selected for a potential combination */ integer nr = length(remaining) -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if (nr = 1) or (ns+nr = ne) then combis = append(combis, selected & remaining) return elsif ns = ne then combis = append(combis, selected) return else integer r1 = remaining[1] remaining = remove(remaining, 1) -- select next recurse(remaining, ne, append(selected, r1)) -- skip next recurse(remaining, ne, selected) end if end procedure function combinations(sequence s, integer lg) combis = {} recurse(s, lg) return combis end function
Jean-Marc
13. Re: Get all combinations of n digits among m digits
- Posted by jmduro 1 week ago
- 147 views
Damned, there was still an error, always due to the position of this test "if nr=1 then":
sequence combis = {} procedure recurse(sequence remaining, /* digits remaining to compute */ integer needed, /* number of digits needed for a valid combination */ sequence selected = {}) /* digits already selected for a potential combination */ integer nr = length(remaining) -- number of digits remaining to compute integer ns = length(selected) -- number of digits already selected for a potential combination if ns+nr = needed then combis = append(combis, selected & remaining) return end if if ns = needed then combis = append(combis, selected) return end if if nr = 1 then return end if -- select next recurse(remove(remaining, 1), needed, selected & remaining[1]) -- skip next recurse(remove(remaining, 1), needed, selected) end procedure function combinations(sequence s, integer lg) combis = {} recurse(s, lg) return combis end function
Jean-Marc
14. Re: Get all combinations of n digits among m digits
- Posted by petelomax 1 week ago
- 142 views
Damned, there was still an error
LOL. I wonder if what you're actually missing is this:
function combinations(sequence s, integer lg) combis = {} if lg<=length(s) then recurse(s, lg) end if return combis end function
and then there should be no need to ever test nr=1.
Plus of course potentially skipping a whole pile of completely pointless effort.
PS: lg=0 currently returns {{}}, which I think is correct. There are probably pros and cons to making it return {} instead,
15. Re: Get all combinations of n digits among m digits
- Posted by jmduro 1 week ago
- 68 views
I have tested your modifications on some hard to resolve grids.
It fails on this one:
{
{{1,3,6,7},{1,6},{1,3,5,6,7,8},{1,3,4,5,6,8},9,{1,3,4,5,8},2,{5,6,8},{4,5,6,8}},
{{2,3,6},4,{2,3,5,6,8,9},{3,5,6,8},{3,6},{3,5,8},1,7,{5,6,8,9}},
{{1,6},{1,6,9},{1,5,6,8,9},2,{1,4,6},7,3,{5,6,8,9},{4,5,6,8,9}},
{{1,2,4,6},3,{1,2,4,6},{1,4,6,8},5,{1,4,8},{6,7,8,9},{1,2,6,8,9},{2,6,7,8,9}},
{9,7,{1,2,4,6},{1,3,4,6,8},{1,2,3,4,6},{1,3,4,8},{6,8},{1,2,3,5,6,8},{2,3,5,6,8}},
{5,8,{1,2,6},9,{1,2,3,6,7},{1,3},{6,7},4,{2,3,6,7}},
{8,{1,6,9},{1,3,4,6,9},7,{1,3,4},2,5,{3,6,9},{3,4,6,9}},
{{1,2,3,4,7},{1,2,9},{1,2,3,4,7,9},{1,3,4,5},{1,3,4},6,{4,7,8,9},{2,3,8,9},{2,3,4,7,8,9}},
{{2,3,4,6,7},5,{2,3,4,6,7,9},{3,4},8,{3,4,9},{4,6,7,9},{2,3,6,9},1}
}
At the end of the game, some cells have no valid candidate at all because of bad tips. I did not check much more because I already had such kind of problems before. I suppose it is due to combinations such as {4,6,6} with repeated candidates that I do not manage. It does not happen with my proposal.
Jean-Marc

