Is there an efficient way in Python to get all partitions of a list of size n
into two subsets of size n/2
? I want to get some iterative construct
Here's a solution which doesn't use itertools. It uses a trick called Gosper's hack to generate bit permutations. See HAKMEM Item 175 for an explanation of how it works; this hack is also mentioned in the Wikipedia article Combinatorial number system. And it features in the accepted answer to this SO question: Iterating over all subsets of a given size.
The parts
function is a generator, so you can use it in a for
loop, as illustrated in my test.
To partiton a list of length n into pairs of sublists of length n/2 we use a binary number bits
consisting of n/2 zero bits and n/2 one bits. A zero bit in a given position indicates that the corresponding list element goes into the left sublist, a one bit in a given position indicates that the corresponding list element goes into the right sublist.
Initially, bits
is set to 2 ** (n/2) - 1
, so if n = 6, bits
starts out as 000111
.
The generator uses Gosper's hack to permute bits
in numerical order, stopping when we get a one bit in the highest position, since that's when we start getting the reversed versions of our sublist pairs.
The code responsible for converting the pattern in bit
into the pair of sublists is:
for i, u in enumerate(lst):
ss[bits & (1<
If there's a zero at bit position i
in bits
then ss[0]
gets the current item from lst
, otherwise it's appended to ss[1]
.
This code runs on Python 2 and Python 3.
from __future__ import print_function
def parts(lst):
''' Generate all pairs of equal-sized partitions of a list of even length '''
n = len(lst)
if n % 2 != 0:
raise ValueError('list length MUST be even')
lim = 1 << (n - 1)
bits = (1 << n // 2) - 1
while bits < lim:
#Use bits to partition lst
ss = [[], []]
for i, u in enumerate(lst):
ss[bits & (1<> 2)
# Test
lst = list(range(1, 7))
for i, t in enumerate(parts(lst), 1):
print('{0:2d}: {1}'.format(i, t))
output
1: [[1, 2, 3], [4, 5, 6]]
2: [[1, 2, 4], [3, 5, 6]]
3: [[1, 3, 4], [2, 5, 6]]
4: [[2, 3, 4], [1, 5, 6]]
5: [[1, 2, 5], [3, 4, 6]]
6: [[1, 3, 5], [2, 4, 6]]
7: [[2, 3, 5], [1, 4, 6]]
8: [[1, 4, 5], [2, 3, 6]]
9: [[2, 4, 5], [1, 3, 6]]
10: [[3, 4, 5], [1, 2, 6]]
I admit that using something inscrutable like Gosper's hack isn't exactly Pythonic. :)
Here's how you capture the output of parts
into a list of all the sublists. It also illustrates that parts
can handle string input, although it produces the output as lists of strings.
seq = list(parts('abcd'))
print(seq)
output
[[['a', 'b'], ['c', 'd']], [['a', 'c'], ['b', 'd']], [['b', 'c'], ['a', 'd']]]
Here's another solution, using itertools to generate the combinations. It generates the pairs in a different order to the earlier version. However, it's shorter and easier to read. More importantly, it's significantly faster, between 50 to 100 percent faster in my timeit
tests, depending on the list length; the difference appears to get smaller for longer lists.
def parts(lst):
n = len(lst)
if n % 2 != 0:
raise ValueError('list length MUST be even')
first = lst[0]
for left in combinations(lst, n // 2):
if left[0] != first:
break
right = [u for u in lst if u not in left]
yield [list(left), right]
# Test
lst = list(range(1, 7))
for i, t in enumerate(parts(lst), 1):
print('{0:2d}: {1}'.format(i, t))
output
1: [[1, 2, 3], [4, 5, 6]]
2: [[1, 2, 4], [3, 5, 6]]
3: [[1, 2, 5], [3, 4, 6]]
4: [[1, 2, 6], [3, 4, 5]]
5: [[1, 3, 4], [2, 5, 6]]
6: [[1, 3, 5], [2, 4, 6]]
7: [[1, 3, 6], [2, 4, 5]]
8: [[1, 4, 5], [2, 3, 6]]
9: [[1, 4, 6], [2, 3, 5]]