在Python中重复的排列

时间:2013-01-15 01:19:46

标签: python algorithm permutation

我想迭代大小为1的n维多维数据集的所有顶点。我知道我可以使用itertools.product执行此操作,如下所示:

>>> n = 3
>>> for j in it.product((0,1), repeat=n) :
...     print j
... 
(0, 0, 0)
(0, 0, 1)
(0, 1, 0)
(0, 1, 1)
(1, 0, 0)
(1, 0, 1)
(1, 1, 0)
(1, 1, 1)

但我需要区别对待每个顶点,具体取决于在其坐标中找到的1的数量,即(0, 1, 1)(1, 0, 1)(1, 1, 0)将全部得到相同的tratment,因为他们都有两个1 s。而不是使用上面的迭代器,然后计算1的数量,我想生成按1 s的数量排序的笛卡尔积,类似于:

>>> for ones in xrange(n) :
...     for seq in magic_expression(ones, n) :
...         print ones, seq
... 
0 (0, 0, 0)
1 (0, 0, 1)
1 (0, 1, 0)
1 (1, 0, 0)
2 (0, 1, 1)
2 (1, 0, 1)
2 (1, 1, 0)
3 (1, 1, 1)

我的高中数学老师会称之为一次取n个2个元素的排列,其中第一个元素重复n - ones次,第二个元素重复ones }次,很容易证明它们有n! / ones! / (n - ones)!

根据wikipedia,我可以按字典顺序生成它们,如下所示:

def lexicographical(ones, n) :
    perm = [0] * (n - ones) + [1] * ones
    yield tuple(perm)
    while True :
        k = None
        for j in xrange(n - 1) :
            if perm[j] < perm[j + 1] :
                k = j
        if k is None :
            break
        l = k + 1
        for j in xrange(k + 2, n) :
            if perm[k] < perm[j] :
                l = j
        perm[k], perm[l] = perm[l], perm[k]
        perm[k+1:] = perm[-1:k:-1]
        yield tuple(perm)

但要计时,这只能开始反映计算n >= 10的完整笛卡尔积,然后只计算ones < 2,这不是典型的用例。是否有一种优雅的方式来加速我的代码,可能是使用一些强大的itertools巫毒,或者完全使用不同的算法?如果它有所不同,我不会太在意所产生的排列的顺序。或者我应该让自己辞职?


修改

我对提出的解决方案做了一些时间安排。按照itertools.product的顺序使用顶点会生成它们,计数总是最快的。但是为了让它们按照一些数量排序,Eevee对列表进行排序的解决方案对于n <= 6是最快的,从那时起Cam的解决方案是两者中最快的。

我接受了Cam的解决方案,因为它更好地回复了被问到的问题。但就我将在我的代码中实现的内容而言,我将自己辞职。

5 个答案:

答案 0 :(得分:5)

如果您编写了超过8行代码来生成8个常量值,那么就会出现问题。

如果没有嵌入我想要的列表,我会以愚蠢的方式做到:

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

除非您使用1000超立方体,否则不应该有任何巨大的性能担忧。

答案 1 :(得分:3)

(低效)替代方法:

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

或者作为元组:

>>> [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(_, 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)]

或使用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)]

答案 2 :(得分:2)

对于3d立方体的使用案例,Eevee的解决方案是正确的。

然而,为了好玩并展示itertools的强大功能,这里有一个线性时间解决方案,可以推广到更高的维度:

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]

答案 3 :(得分:1)

根据您对顶点的处理方式进行计数并不是一个坏主意,因为如果必须迭代所有这些顶点,O(f(n))至少为O(f(n)* 2 n),对它们进行排序为O(n * 2 n)。所以它基本上取决于f(n)专业n。

除此之外,这是一个可能的神奇表达:

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

permutations with unique values的帮助下。

这是有效的,因为itertools.permutations会产生排序结果。请注意,a最初是排序的,因为首先是零。

答案 4 :(得分:1)

以下是一些运行速度更快的代码(适用于中等n),比Cam或Eevee快几倍(适用于大n)。随后进行时间比较。

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

下面显示的时序结果来自ipython中的一系列%timeit次调用。每次通话都是一种形式 %timeit [x for x in cube1s.f(n)]
使用cornersjc, cornerscc, cornersec, cornerses代替f(代表我的代码,Cam代码,Eevee代码和我的Eevee方法版本)以及代替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

上面给出了cornersjc的代码。 cornerscccornerseccornerses的代码如下。这些产生与cornersjc相同的输出,除了Cam的代码产生列表而不是元组列表,并且每个位计数组内产生相反的结果。

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

注意,cornersjc的最后三行可以替换为

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

更干净但更慢。请注意,如果使用的是yield v而不是yield v[0]+v[1],则代码的运行速度会快于cornersjc,但(在n=5处)会产生一对元组结果,如((1,0) ),(1,1,0));当使用yield v[0]+v[1]时,代码运行速度比cornersjc慢,但产生相同的结果,元组列表如(1,0,1,1,0)。 下面是一个示例时间,cornersjp是修改后的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