N choose N/2 sublists of a list

前端 未结 5 793
抹茶落季
抹茶落季 2021-01-14 20:27

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

5条回答
  •  被撕碎了的回忆
    2021-01-14 20:53

    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.

    How it works.

    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]]
    

提交回复
热议问题