Reputation: 624
I need to select 6 integers from range(1, 51)
such that no two consecutive integers are selected. (1, 3, 6, 9, 13, 28)
is a valid selection but (1, 3, 4, 9, 13, 28)
is not. I need to build a list of all such possible combinations, with each combination in a tuple. Instead of a list a generator will also do. I understand I need to use something like itertools.combinations
here, but I can't figure out how to eliminate the tuples with consecutive values. I wrote this code,
>>> import itertools
>>> l = list(itertools.combinations(range(1, 51), 6))
>>> len(l)
13983816
That is the length I am expecting if there were no constraints on what tuples can be selected, i.e, 50!/(44!6!)
. Any help?
Upvotes: 3
Views: 291
Reputation: 281624
from itertools import combinations, imap
from operator import add
from functools import partial
result = imap(partial(map, add, range(6)), combinations(range(1, 46), 6))
This solution makes use of a bijection from the desired combinations to the set of all combinations of 6 integers from 1 to 45. We pick 6 increasing numbers x0, x1, x2, x3, x4, x5
from 1 to 45, then compute
x0, x1+1, x2+2, x3+3, x4+4, x5+5
This new combination is guaranteed to fall within the range 1-50 and have no consecutive numbers, and any desired combination y0, y1, y2, y3, y4, y5
can be produced by the unique choice of x values
y0, y1-1, y2-2, y3-3, y4-4, y5-5
This works a few times faster than a solution based around filtering out the undesired combinations. The downside is that it takes longer to understand. I had to write a substantial amount of explanation for this, while the other solution is much more straightforward. With longer combinations, this algorithm would have a substantial advantage. For example, if we were picking 16 numbers instead of 6, the other algorithm would consider about 1212 times the number of combinations this algorithm would.
Upvotes: 5
Reputation: 46425
If you think about it, your first (lowest) value cannot be "any one of 50" values - it has to be less than or equal to 40 since (40, 42, 44, 46, 48, 50)
is the "last valid tuple". There must always be 5 "unused values" in between the ones you pick, so the simplest way to achieve this is to select "unconstrained" values from 1 to 45, then add 0, 1, 2, ... 5 to the (sorted) values you get.
Thus:
for t in itertools.combinations(range(1,46), 6):
print tuple(x+y for x,y in zip(t, (0,1,2,3,4,5)))
This is more efficient as it doesn't generate any combinations it won't use. With the numbers you are talking about that is significant. Original = 50!/(44!*6!)
, new = 45!/(39!*6!)
. That makes it about 2x more efficient (50*49*48*47*46*45 / (45*44*43*42*41*40)
) ~ 1.95
... (and thanks user2357112 for pointing out the glaring arithmetic mistake I made - there was an extra factor 6!
that had crept in...)
Upvotes: 3
Reputation: 251106
You can use all
with a generator expression:
>>> t = (1,3,4,9,13,28)
>>> all( x-y > 1 for x, y in zip(t[1:], t))
False
>>> t = (1,3,6,9,13,28)
>>> all( x-y > 1 for x, y in zip(t[1:], t))
True
Code:
import itertools
for t in itertools.combinations(range(1, 20), 6):
if all( x-y > 1 for x, y in zip(t[1:], t)):
#do something with t
Upvotes: 7