2015-02-07 3 views
60

In der Festkörpermechanik, verwende ich Python oft und schreiben Code, der wie folgt aussieht:Wie kann ich in Python eine For-Loop-Pyramide prägnanter gestalten?

for i in range(3): 
    for j in range(3): 
     for k in range(3): 
      for l in range(3): 
       # do stuff 

ich dies oft wirklich tun, dass ich beginnen sich zu fragen, ob es eine prägnante Art und Weise, dies zu tun. Der Nachteil des aktuellen Codes ist: wenn ich PEP8 erfülle, dann kann ich die 79-Zeichen-Grenze pro Zeile nicht überschreiten, und es ist nicht zu viel Platz übrig, besonders wenn dies wieder in Abhängigkeit von einer Klasse ist.

+0

Sie durchlaufen nur Bereiche? Dann gibt es einen kürzeren (wenn auch nicht unbedingt lesbareren) Weg. – L3viathan

+5

Wenn ein Algorithmus O (n^4) ist, dann ist es O (n^4). Umsonst. Um die Grenze von 79 Zeichen zu umgehen, sollten Sie sie in Funktionen aufteilen. Das wird sowohl für die Lesbarkeit als auch für die Testbarkeit Wunder wirken. – SuperSaiyan

+5

Gut ...Deep Nested Looping ist keine sehr nette Art zu programmieren ... also denke ich, dass Sie sich mehr darum kümmern sollten, tief verschachteltes Looping zu vermeiden als über PEP8. –

Antwort

116

Nach dem, was Sie tun möchten, können Sie das itertools Modul verwenden, um die for Schleifen (oder zip) .In diesem Fall itertools.product schaffen würde, was man mit den vier Schleifen getan haben, zu minimieren:

>>> list(product(range(3),repeat=4)) 
[(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 1, 0), (0, 0, 1, 1), 
(0, 0, 1, 2), (0, 0, 2, 0), (0, 0, 2, 1), (0, 0, 2, 2), (0, 1, 0, 0), 
(0, 1, 0, 1), (0, 1, 0, 2), (0, 1, 1, 0), (0, 1, 1, 1), (0, 1, 1, 2), 
(0, 1, 2, 0), (0, 1, 2, 1), (0, 1, 2, 2), (0, 2, 0, 0), (0, 2, 0, 1), 
(0, 2, 0, 2), (0, 2, 1, 0), (0, 2, 1, 1), (0, 2, 1, 2), (0, 2, 2, 0), 
(0, 2, 2, 1), (0, 2, 2, 2), (1, 0, 0, 0), (1, 0, 0, 1), (1, 0, 0, 2), 
(1, 0, 1, 0), (1, 0, 1, 1), (1, 0, 1, 2), (1, 0, 2, 0), (1, 0, 2, 1), 
(1, 0, 2, 2), (1, 1, 0, 0), (1, 1, 0, 1), (1, 1, 0, 2), (1, 1, 1, 0), 
(1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 2, 0), (1, 1, 2, 1), (1, 1, 2, 2), 
(1, 2, 0, 0), (1, 2, 0, 1), (1, 2, 0, 2), (1, 2, 1, 0), (1, 2, 1, 1), 
(1, 2, 1, 2), (1, 2, 2, 0), (1, 2, 2, 1), (1, 2, 2, 2), (2, 0, 0, 0), 
(2, 0, 0, 1), (2, 0, 0, 2), (2, 0, 1, 0), (2, 0, 1, 1), (2, 0, 1, 2), 
(2, 0, 2, 0), (2, 0, 2, 1), (2, 0, 2, 2), (2, 1, 0, 0), (2, 1, 0, 1), 
(2, 1, 0, 2), (2, 1, 1, 0), (2, 1, 1, 1), (2, 1, 1, 2), (2, 1, 2, 0), 
(2, 1, 2, 1), (2, 1, 2, 2), (2, 2, 0, 0), (2, 2, 0, 1), (2, 2, 0, 2), 
(2, 2, 1, 0), (2, 2, 1, 1), (2, 2, 1, 2), (2, 2, 2, 0), (2, 2, 2, 1), 
(2, 2, 2, 2)] 

und in Ihrem Code können Sie tun:

for i,j,k,l in product(range(3),repeat=4): 
    #do stuff 

Diese Funktion wie dem folgenden Code entspricht, mit der Ausnahme, dass die tatsächliche Implementierung nicht bui tut ld up Zwischenergebnisse im Speicher:

def product(*args, **kwds): 
    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy 
    # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 
    pools = map(tuple, args) * kwds.get('repeat', 1) 
    result = [[]] 
    for pool in pools: 
     result = [x+[y] for x in result for y in pool] 
    for prod in result: 
     yield tuple(prod) 

bearbeiten: Wie @ PeterE in Kommentar sagt product() kann auch verwendet werden, wenn die Bereiche unterschiedlicher Länge haben:

product(range(3),range(4),['a','b','c'] ,some_other_iterable) 
+7

IMHO, das ist der Weg zu gehen. Zumindest wenn die Schleifen so einfach sind wie im OP dargestellt. Dieses Schema kann auch Bereiche unterschiedlicher Länge behandeln (tatsächlich kann es beliebige iterierbare Werte verarbeiten): 'product (Bereich (3), Bereich (4), ['a', 'b', 'c'], some_other_iterable)'. Wenn die verschachtelten Schleifen komplexer als das sind (z. B. einige if/else-Logik enthalten), ist die Definition der On-Generator-Funktion das nächstbeste. Hier würde ich wegen der Einfachheit und des einfachen Verständnisses @ SergeBalestas Variante über @ L3viathan bevorzugen. – PeterE

+0

@PeterE Ich denke, Sie können die Einfachheit in 'für i, j, k, l im Produkt finden (Bereich (3), wiederholen = 4)' auch ich die äquivalente Funktion von 'Produkt' hinzufügen, um zu antworten, können Sie es überprüfen aus . – Kasramvd

+0

Rückblickend sehe ich, dass mein Kommentar als Kritiker missverstanden werden könnte. Das war nicht meine Absicht. Ich wollte ausdrücken, dass ich Ihre Lösung für das gegebene Problem am besten finde. Und ich möchte erwähnen, dass 'product()' immer noch verwendet werden kann, auch wenn die Bereiche unterschiedliche Länge haben. – PeterE

7

Dies entspricht:

for c in range(3**4): 
    i = c // 3**3 % 3 
    j = c // 3**2 % 3 
    k = c // 3**1 % 3 
    l = c // 3**0 % 3 
    print(i,j,k,l) 

Wenn Sie dies die ganze Zeit tun, sollten Sie ein Gen verwenden ral Generator für sie:

def nestedLoop(n, l): 
    return ((tuple((c//l**x%l for x in range(n-1,-1,-1)))) for c in range(l**n)) 

for (a,b,c,d) in nestedLoop(4,3): 
    print(a,b,c,d) 
+0

'c // 1'? Egal, guter Punkt ... den ich vielleicht gewählt hätte, wenn Sie zumindest gezeigt hätten, wie man diesen speziellen Fall in einen Generator umwandeln kann. – martineau

+0

Ich habe 'c // 1' (und den ersten Modulo) für ein besseres Verständnis enthalten. – L3viathan

+1

In diesem Fall hätten Sie vermutlich auch 'c // 3 ** 3% 3',' c // 3 ** 2% 3', usw. '' '' – martineau

13

Es ist nicht mehr prägnant sein wird, wie es Ihnen eine Generatorfunktion kostet, aber zumindest werden Sie nicht von PEP8 belästigt werden:

def tup4(n): 
    for i in range(n): 
     for j in range(n): 
      for k in range(n): 
       for l in range(n): 
        yield (i, j, k, l) 

for (i, j, k, l) in tup4(3): 
    # do your stuff 

(in Python 2.x Sie xrange statt range in der Generatorfunktion)

EDIT verwenden sollte:

obige Verfahren sollte in Ordnung sein, wenn die Tiefe der Pyramide ist kno wn. Sie können aber auch einen generischen Generator ohne externe Modul auf diese Weise machen:

def tup(n, m): 
    """ Generate all different tuples of size n consisting of integers < m """ 
    l = [ 0 for i in range(n)] 
    def step(i): 
     if i == n : raise StopIteration() 
     l[i] += 1 
     if l[i] == m: 
      l[i] = 0 
      step(i+ 1) 
    while True: 
     yield tuple(l) 
     step(0) 

for (l, k, j, i) in tup(4, 3): 
    # do your stuff 

(I verwendet (l, k, j, i) da oben Generator, erster Index zuerst variiert)

15

Die Idee zu verwenden itertools.product gut ist. Hier ist ein allgemeinerer Ansatz, der Bereiche unterschiedlicher Größe unterstützt.

from itertools import product 

def product_of_ranges(*ns): 
    for t in product(*map(range, ns)): 
     yield t 

for i, j, k in product_of_ranges(4, 2, 3): 
    # do stuff 
Verwandte Themen