Permutations with repetition in Python

后端 未结 5 1282
囚心锁ツ
囚心锁ツ 2021-02-10 03:47

I want to iterate over all the vertices of an n dimensional cube of size 1. I know I could do that with itertools.product as follows:

&         


        
相关标签:
5条回答
  • 2021-02-10 03:52

    For your use-case of 3d cubes, Eevee's solution is the correct one.

    However for fun and to demonstrate the power of itertools, here's a linear-time solution that generalizes to higher dimensions:

    from itertools import combinations
    
    # n is the number of dimensions of the cube (3 for a 3d cube)
    def generate_vertices(n):
        for number_of_ones in xrange(0, n + 1):
            for location_of_ones in combinations(xrange(0, n), number_of_ones):
                result = [0] * n
                for location in location_of_ones:
                    result[location] = 1
                yield result
    
    for vertex in generate_vertices(3):
        print vertex
    
    
    # result:
    # [0, 0, 0]
    # [1, 0, 0]
    # [0, 1, 0]
    # [0, 0, 1]
    # [1, 1, 0]
    # [1, 0, 1]
    # [0, 1, 1]
    # [1, 1, 1]
    
    0 讨论(0)
  • 2021-02-10 03:54

    It is not a bad idea to count depending on what you will do with the vertices because if you have to iterate over all of them doing something O(f(n)) is at least O(f(n)*2n), sorting them is O(n*2n). So it basically depends if f(n) majors n.

    Aside from that here is a possible magic expression:

    def magic_expression(ones, n):
        a = (0,) * (n - ones) + (1,) * ones
        previous = tuple()
        for p in itertools.permutations(a):
            if p > previous:
                previous = p
                yield p
    

    With help from permutations with unique values.

    This works because itertools.permutations yield sorted results. Note that a is initially sorted because zeros come first.

    0 讨论(0)
  • 2021-02-10 03:56

    An (inefficient) alternative method:

    >>> ['{0:03b}'.format(x) for x in range(8)]
    ['000', '001', '010', '011', '100', '101', '110', '111']
    

    Or as tuples:

    >>> [tuple(int(j) for j in list('{0:03b}'.format(x))) for x in range(8)]
    
    [(0, 0, 0),
     (0, 0, 1),
     (0, 1, 0),
     (0, 1, 1),
     (1, 0, 0),
     (1, 0, 1),
     (1, 1, 0),
     (1, 1, 1)]
    

    Sorted by number of vertices:

    >>> sorted(_, key=lambda x: sum(x))
    
    [(0, 0, 0),
     (0, 0, 1),
     (0, 1, 0),
     (1, 0, 0),
     (0, 1, 1),
     (1, 0, 1),
     (1, 1, 0),
     (1, 1, 1)]
    

    Or using itertools:

    >>> sorted(itertools.product((0, 1), repeat=3), key=lambda x: sum(x))
    
    [(0, 0, 0),
     (0, 0, 1),
     (0, 1, 0),
     (1, 0, 0),
     (0, 1, 1),
     (1, 0, 1),
     (1, 1, 0),
     (1, 1, 1)]
    
    0 讨论(0)
  • 2021-02-10 04:01

    If you've written more than eight lines of code to generate eight constant values, something has gone wrong.

    Short of just embedding the list I want, I'd do it the dumb way:

    vertices = (
        (v.count(1), v)
        for v in itertools.product((0, 1), repeat=3)
    )
    for count, vertex in sorted(vertices):
        print vertex
    

    Unless you're working with 1000-hypercubes, you shouldn't have any huge performance worries.

    0 讨论(0)
  • 2021-02-10 04:06

    Following is some code that runs faster (for medium n) and several times faster (for large n) than that of Cam or Eevee. A time comparison follows.

    def cornersjc (n):   # Re: jw code
        from itertools import product
        m = (n+1)/2
        k = n-m
        # produce list g of lists of tuples on k bits
        g = [[] for i in range(k+1)]
        for j in product((0,1), repeat=k):
            g[sum(j)].append(tuple(j))
        # produce list h of lists of tuples on m bits
        if k==m:
            h = g
        else:
            h = [[] for i in range(m+1)]
            for j in product((0,1), repeat=m):
                h[sum(j)].append(tuple(j))
        # Now deliver n-tuples in proper order
        for b in range(n+1):  # Deliver tuples with b bits set
            for lb in range(max(0, b-m), min(b+1,k+1)):
                for l in g[lb]:
                    for r in h[b-lb]:
                        yield l+r
    

    The timing results shown below are from a series of %timeit calls in ipython. Each call was of a form like
    %timeit [x for x in cube1s.f(n)]
    with the names cornersjc, cornerscc, cornersec, cornerses in place of f (standing for my code, Cam's code, Eevee's code, and my version of Eevee's method) and a number in place of n.

    n    cornersjc    cornerscc    cornersec    cornerses
    
    5      40.3 us      45.1 us      36.4 us      25.2 us    
    6      51.3 us      85.2 us      77.6 us      46.9 us    
    7      87.8 us      163 us       156 us       88.4 us    
    8     132 us       349 us       327 us       178 us    
    9     250 us       701 us       688 us       376 us    
    10    437 us      1.43 ms      1.45 ms       783 us
    11    873 us      3 ms         3.26 ms      1.63 ms
    12   1.87 ms      6.66 ms      8.34 ms      4.9 ms
    

    Code for cornersjc was given above. Code for cornerscc, cornersec, and cornerses is as follows. These produce the same output as cornersjc, except that Cam's code produces a list of lists instead of a list of tuples, and within each bit-count group produces in reverse.

    def cornerscc(n):   # Re: Cam's code
        from itertools import combinations
        for number_of_ones in xrange(0, n + 1):
            for location_of_ones in combinations(xrange(0, n), number_of_ones):
                result = [0] * n
                for location in location_of_ones:
                    result[location] = 1
                yield result
    
    def cornersec (n):   # Re:  Eevee's code
        from itertools import product
        vertices = ((v.count(1), v)
                    for v in product((0, 1), repeat=n))
        for count, vertex in sorted(vertices):
            yield vertex
    
    def cornerses (n):   # jw mod. of Eevee's code
        from itertools import product
        for vertex in sorted(product((0, 1), repeat=n), key=sum):
            yield vertex
    

    Note, the last three lines of cornersjc can be replaced by

                for v in product(g[lb], h[b-lb]):
                    yield v[0]+v[1]
    

    which is cleaner but slower. Note, if yield v is used instead of yield v[0]+v[1], the code runs faster than cornersjc but (at n=5) produces pair-of-tuple results like ((1, 0), (1, 1, 0)); when yield v[0]+v[1] is used, the code runs slower than cornersjc but produces identical results, a list of tuples like (1, 0, 1, 1, 0). An example timing follows, with cornersjp being the modified cornersjc.

    In [93]: for n in range(5,13):
        %timeit [x for x in cube1s.cornersjp(n)]
       ....:     
    10000 loops, best of 3: 49.3 us per loop
    10000 loops, best of 3: 64.9 us per loop
    10000 loops, best of 3: 117 us per loop
    10000 loops, best of 3: 178 us per loop
    1000 loops, best of 3: 351 us per loop
    1000 loops, best of 3: 606 us per loop
    1000 loops, best of 3: 1.28 ms per loop
    100 loops, best of 3: 2.74 ms per loop
    
    0 讨论(0)
提交回复
热议问题