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:
&
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]
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.
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)]
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.
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