I was solving a programming puzzle involving combinations. It led me to a wonderful itertools.combinations
function and I'd like to know how it works under the hood. Documentation says that the algorithm is roughly equivalent to the following:
def combinations(iterable, r):
# combinations('ABCD', 2) --> AB AC AD BC BD CD
# combinations(range(4), 3) --> 012 013 023 123
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = list(range(r))
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
else:
return
indices[i] += 1
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
yield tuple(pool[i] for i in indices)
I got the idea: we start with the most obvious combination (r
first consecutive elements). Then we change one (last) item to get each subsequent combination.
The thing I'm struggling with is a conditional inside for
loop.
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
This experession is very terse, and I suspect it's where all the magic happens. Please, give me a hint so I could figure it out.
The itertools. combinations() function takes two arguments—an iterable inputs and a positive integer n —and produces an iterator over tuples of all combinations of n elements in inputs .
If we select k things from a total of n options and we don't care in what order they are, the total number of combinations (the number of different ways to do this) is: C(n,k)=(nk)=n!k! (n−k)! The total number of distinct 5-card poker hands is C(52,5)=2,598,960.
To create combinations without using itertools, iterate the list one by one and fix the first element of the list and make combinations with the remaining list. Similarly, iterate with all the list elements one by one by recursion of the remaining list.
The unique combination of two lists in Python can be formed by pairing each element of the first list with the elements of the second list. Method 1 : Using permutation() of itertools package and zip() function. Approach : Import itertools package and initialize list_1 and list_2.
The loop has two purposes:
Let us say you have an iterable over 5 elements, and want combinations of length 3. What you essentially need for this is to generate lists of indexes. The juicy part of the above algorithm generates the next such index-list from the current one:
# obvious
index-pool: [0,1,2,3,4]
first index-list: [0,1,2]
[0,1,3]
...
[1,3,4]
last index-list: [2,3,4]
i + n - r
is the max value for index i
in the index-list:
index 0: i + n - r = 0 + 5 - 3 = 2
index 1: i + n - r = 1 + 5 - 3 = 3
index 2: i + n - r = 2 + 5 - 3 = 4
# compare last index-list above
=>
for i in reversed(range(r)): if indices[i] != i + n - r: break else: break
This loops backwards through the current index-list and stops at the first position that doesn't hold its maximum index-value. If all positions hold their maximum index-value, there is no further index-list, thus return
.
In the general case of [0,1,4]
one can verify that the next list should be [0,2,3]
. The loop stops at position 1
, the subsequent code
indices[i] += 1
increments the value for indeces[i]
(1 -> 2
). Finally
for j in range(i+1, r): indices[j] = indices[j-1] + 1
resets all positions > i
to the smallest legal index-values, each 1
larger than its predecessor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With