Generating all unique pair permutations

孤人 提交于 2019-11-28 11:19:33

I'd like to share a different implementation of round-robin scheduling that makes use of the deque-data structure from the Standard Library:

from collections import deque

def round_robin_even(d, n):
    for i in range(n - 1):
        yield [[d[j], d[-j-1]] for j in range(n/2)]
        d[0], d[-1] = d[-1], d[0]
        d.rotate()

def round_robin_odd(d, n):
    for i in range(n):
        yield [[d[j], d[-j-1]] for j in range(n/2)]
        d.rotate()

def round_robin(n):
    d = deque(range(n))
    if n % 2 == 0:
        return list(round_robin_even(d, n))
    else:
        return list(round_robin_odd(d, n))


print round_robin(5)
  [[[0, 4], [1, 3]],
   [[4, 3], [0, 2]],
   [[3, 2], [4, 1]],
   [[2, 1], [3, 0]],
   [[1, 0], [2, 4]]]


print round_robin(2)
   [[[0, 1]]]

It puts the objects(ints here) in the deque. Then it rotates and builds consecutive pairs taking from both ends towards the middle. One can imagine this as folding the deque in the middle back on itself. To make it clear:

Case uneven elements:

 round 1     round 2       # pairs are those numbers that sit
----------  ---------      # on top of each other
0 1 2 3 4   8 0 1 2 3
8 7 6 5     7 6 5 4

In case of even elements an additional step is required.
(I missed the first time cause I only checked the uneven case. This yielded a horribly wrong algorithm... which shows me how important it is to check edge cases when implementing an algorithm...)
This special step is that I swap the two leftmost elements (which are the first and last elements of the deque) before each rotation -- this means the 0 stays all the time upper left.

Case even elements:

 round 1     round 2       # pairs are those numbers that sit
----------  ---------      # on top of each other
0 1 2 3     0 7 1 2
7 6 5 4     6 5 4 3

What haunts me about this version is the amount of code duplication, but I couldn't find a way to improve while keeping it as readable. Here's my first implementation, which is less readable IMO:

def round_robin(n):
    is_even = (n % 2 == 0)
    schedule = []
    d = deque(range(n))
    for i in range(2 * ((n - 1) / 2) + 1):
        schedule.append(
                        [[d[j], d[-j-1]] for j in range(n/2)])
        if is_even:
            d[0], d[-1] = d[-1], d[0]
        d.rotate()
    return schedule

Update to account for the updated question:

To allow in the uneven case for groups of three you just need to change round_robin_odd(d, n):

def round_robin_odd(d, n):
    for i in range(n):
        h = [[d[j], d[-j-1]] for j in range(n/2)]
        h[-1].append(d[n/2])
        yield h
        d.rotate()

This gives:

print round_robin(5)
[[[0, 4], [1, 3, 2]],
 [[4, 3], [0, 2, 1]],
 [[3, 2], [4, 1, 0]],
 [[2, 1], [3, 0, 4]],
 [[1, 0], [2, 4, 3]]]

Pass the list to set to get make sure each tuple only exists once.

>>> from itertools import permutations
>>> set( [ zip( perm[::2], perm[1::2] ) for perm in permutations( range( 9 ) ) ] )
set([(7, 3), (4, 7), (1, 3), (4, 8), (5, 6), (2, 8), (8, 0), (3, 2), (2, 1), (6, 2), (1, 6), (5, 1), (3, 7), (2, 5), (8, 5), (0, 3), (5, 8), (4, 0), (1, 2), (3, 8), (3, 1), (6, 7), (2, 0), (8, 1), (7, 6), (3, 0), (6, 3), (1, 5), (7, 2), (3, 6), (0, 4), (8, 6), (3, 5), (4, 1), (6, 4), (5, 4), (2, 6), (8, 2), (2, 7), (7, 1), (4, 5), (8, 3), (1, 4), (6, 0), (7, 5), (2, 3), (0, 7), (8, 7), (4, 2), (1, 0), (0, 8), (6, 5), (4, 6), (0, 1), (5, 3), (7, 0), (6, 8), (3, 4), (6, 1), (5, 7), (5, 2), (0, 2), (7, 4), (0, 6), (1, 8), (4, 3), (1, 7), (0, 5), (5, 0), (7, 8), (2, 4), (8, 4)])

From your description you want each of the 2-tuple permutations of the range( 9 ) the above should give you all of the various permutations, based on your code. But, this is pretty inefficient.

However you can further simplify your code by doing the following:

>>> list( permutations( range( 9 ), 2 ) )
[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (1, 0), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (2, 0), (2, 1), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (3, 0), (3, 1), (3, 2), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (4, 0), (4, 1), (4, 2), (4, 3), (4, 5), (4, 6), (4, 7), (4, 8), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 6), (5, 7), (5, 8), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 7), (6, 8), (7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 8), (8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5), (8, 6), (8, 7)]

The method permutations also takes a length argument that will allow you to specify the length of the tuple returned. So, you were using the correct itertool provided function, but missed the tuple length parameter.

itertools.permutations documentation

Gareth Rees

As MatthieuW says in this answer, it looks as if you are trying to generate a schedule for a round-robin tournament. This can be easily generated using this algorithm, the main difficulty being the handling of an odd number of teams (when each team gets a bye in one round).

def round_robin_schedule(n):
    """
    Generate a round-robin tournament schedule for `n` teams.
    """
    m = n + n % 2               # Round up to even number.
    for r in xrange(m - 1):
        def pairing():
            if r < n - 1:
                yield r, n - 1
            for i in xrange(m // 2 - 1):
                p, q = (r + i + 1) % (m - 1), (m + r - i - 2) % (m - 1)
                if p < n - 1 and q < n - 1:
                    yield p, q
        yield list(pairing())

For example, with nine teams:

>>> list(round_robin_schedule(9))
[[(0, 8), (2, 7), (3, 6), (4, 5)],
 [(1, 8), (2, 0), (4, 7), (5, 6)],
 [(2, 8), (3, 1), (4, 0), (6, 7)],
 [(3, 8), (4, 2), (5, 1), (6, 0)],
 [(4, 8), (5, 3), (6, 2), (7, 1)],
 [(5, 8), (6, 4), (7, 3), (0, 1)],
 [(6, 8), (7, 5), (0, 3), (1, 2)],
 [(7, 8), (0, 5), (1, 4), (2, 3)],
 [(0, 7), (1, 6), (2, 5), (3, 4)]]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!