6

我想遍历n大小为 1 的维度立方体的所有顶点。我知道我可以这样做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在其坐标中找到的 s 的数量,即(0, 1, 1)(1, 0, 1)并且 (1, 1, 0)都会收到相同的处理,因为它们都有两个1s。而不是使用上面的迭代器,然后计算1s 的数量,我想生成按1s 数量排序的笛卡尔积,类似于:

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

我的高中数学老师会称这些为一次取两个元素的排列,其中第一个元素重复多次,第二个nn - onesones元素重复,很容易证明它们存在n! / ones! / (n - ones)!

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

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 的解决方案,因为它更好地回答了所提出的问题。但就我将在我的代码中实现的内容而言,我将让自己接受计数。

4

5 回答 5

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 个超立方体,否则您不应该担心任何巨大的性能问题。

于 2013-01-15T02:10:13.513 回答
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)]
于 2013-01-15T02:37:36.900 回答
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]
于 2013-01-15T02:18:09.827 回答
1

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
于 2013-01-18T21:29:01.453 回答
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

在具有唯一值的排列的帮助下。

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

于 2013-01-15T01:53:03.990 回答