Wie entferne ich mich von der „for-loop“ -Gedankenschule?


79

Dies ist eine eher konzeptionelle Frage, aber ich hatte gehofft, dass ich dazu einen guten Rat bekommen könnte. Ein Großteil der Programmierung, die ich mache, erfolgt mit ( NumPy ) Arrays. Ich muss häufig Elemente in zwei oder mehr Arrays unterschiedlicher Größe zuordnen. Als Erstes gehe ich zu einer for-Schleife oder, noch schlimmer, zu einer verschachtelten for-Schleife. Ich möchte for-Schleifen so weit wie möglich vermeiden, weil sie langsam sind (zumindest in Python).

Ich weiß, dass es für viele Dinge mit NumPy vordefinierte Befehle gibt, die ich nur erforschen muss, aber haben Sie (als erfahrenere Programmierer) einen allgemeinen Denkprozess, der Ihnen einfällt, wenn Sie etwas wiederholen müssen?

Also habe ich oft so etwas, was schrecklich ist und ich möchte es vermeiden:

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]

Ich weiß, dass es mehrere verschiedene Wege gibt, um dies zu erreichen, aber ich bin an einer allgemeinen Denkweise interessiert, falls es sie gibt.


10
Sie suchen nach funktionaler Programmierung : Lambda-Ausdrücke, Funktionen höherer Ordnung, Generieren von Ausdrücken usw. Google diese.
Kilian Foth

42
I want to avoid for-loops as much as possible because they are slow (at least in Python).Klingt so, als würden Sie hier das falsche Problem lösen. Wenn Sie über etwas iterieren müssen, müssen Sie über etwas iterieren. Sie werden einen ähnlichen Leistungstreffer erzielen, egal welches Python-Konstrukt Sie verwenden. Wenn Ihr Code langsam ist, dann nicht, weil Sie forSchleifen haben. Das liegt daran, dass Sie unnötige Arbeit oder Arbeit auf der Python-Seite erledigen, die auf der C-Seite erledigt werden könnte. In Ihrem Beispiel erledigen Sie zusätzliche Arbeit. Du hättest es mit einer Schleife anstatt mit zwei machen können.
Doval

24
@Doval leider nicht - bei NumPy . Eine Python for-Schleife, die eine elementweise Addition durchführt, kann für realistische Array-Größen mehrere Male (!) Langsamer sein als der vektorisierte NumPy-Operator (der nicht nur in C geschrieben ist, sondern SSE-Anweisungen und andere Tricks verwendet).

46
Einige der obigen Kommentare scheinen die Frage falsch zu verstehen. Wenn Sie in NumPy programmieren, erzielen Sie die besten Ergebnisse, wenn Sie Ihre Berechnung vektorisieren können. Ersetzen Sie also explizite Schleifen in Python durch Ganz-Array-Operationen in NumPy. Dies unterscheidet sich konzeptionell stark von der normalen Programmierung in Python und erfordert einige Zeit zum Erlernen. Ich halte es für angemessen, dass das OP um Rat fragt, wie man lernt, es zu tun.
Gareth Rees

3
@PieterB: Ja, das stimmt. "Vektorisierung" ist nicht dasselbe wie "Auswahl des besten Algorithmus". Sie sind zwei verschiedene Ursachen für Schwierigkeiten, wenn es darum geht, effiziente Implementierungen zu finden. Es ist daher am besten, nacheinander darüber nachzudenken.
Gareth Rees

Antworten:


89

Dies ist eine häufige konzeptionelle Schwierigkeit, wenn Sie lernen, NumPy effektiv einzusetzen . Normalerweise wird die Datenverarbeitung in Python am besten in Form von Iteratoren ausgedrückt , um die Speichernutzung gering zu halten, die Möglichkeiten für Parallelität mit dem E / A-System zu maximieren und die Wiederverwendung und Kombination von Teilen von Algorithmen zu ermöglichen.

Aber NumPy dreht all das um: Der beste Ansatz besteht darin, den Algorithmus als Folge von Ganz-Array-Operationen auszudrücken , um den Zeitaufwand im langsamen Python-Interpreter zu minimieren und den Zeitaufwand für schnell kompilierte NumPy-Routinen zu maximieren.

Hier ist mein allgemeiner Ansatz:

  1. Behalten Sie die Originalversion der Funktion bei (von der Sie sicher sind, dass sie korrekt ist), damit Sie sie mit Ihren verbesserten Versionen auf Richtigkeit und Geschwindigkeit testen können.

  2. Arbeiten Sie von innen nach außen: Beginnen Sie mit der innersten Schleife und prüfen Sie, ob eine Vektorisierung möglich ist. Wenn Sie das getan haben, gehen Sie eine Ebene weiter und fahren Sie fort.

  3. Verbringen Sie viel Zeit mit dem Lesen der NumPy-Dokumentation . Es gibt viele Funktionen und Operationen, die nicht immer einen hervorragenden Namen haben. Es lohnt sich also, sie kennenzulernen. Vor allem, wenn Sie denken: "Wenn es nur eine Funktion gibt, die so und so funktioniert", lohnt es sich, zehn Minuten lang danach zu suchen. Es ist normalerweise irgendwo da drin.

Es gibt keinen Ersatz für das Üben, deshalb werde ich Ihnen einige Beispielprobleme nennen. Das Ziel für jedes Problem ist es, die Funktion so umzuschreiben, dass sie vollständig vektorisiert ist : Das heißt, dass sie aus einer Folge von NumPy-Operationen für ganze Arrays besteht, ohne native Python-Schleifen (keine foroder whileAnweisungen, keine Iteratoren oder Verstehen).

Problem 1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

Problem 2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

Problem 3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

Spoiler unten. Sie werden viel die besten Ergebnisse erzielen, wenn Sie es selbst versuchen, bevor Sie sich meine Lösungen ansehen!

Antwort 1

np.sum (x) * np.sum (y)

Antwort 2

np.sum (np.searchsorted (np.sort (x), y))

Antwort 3

np.where (x == fehlt, Wert, x)


Warten Sie, gibt es einen Tippfehler in der letzten Antwort oder ändert NumPy, wie Python den Code interpretiert?
Izkata

1
@Izkata Es ändert nichts an sich, aber logische Operationen, die auf Arrays angewendet werden, geben boolesche Arrays zurück.
Sapi

@sapi Ah, ich habe verpasst, was in den Doktesten vor sich geht, dachte, das wäre klarlist
Izkata

Vielleicht sollte es eine Möglichkeit geben, APL einzubetten?

Ich liebe es, wie du Hausaufgaben machst.
Koray Tugay

8

Um die Dinge schneller zu machen, müssen Sie Ihre Datenstrukturen nachlesen und die entsprechenden verwenden.

Für nicht triviale Größen von kleinen Arrays und großen Arrays (sagen wir kleine = 100 Elemente und große = 10.000 Elemente) besteht eine Möglichkeit darin, das kleine Array zu sortieren, dann über große Arrays zu iterieren und mit einer binären Suche nach passenden Elementen zu suchen in der kleinen Reihe.

Dies würde die maximale Zeitkomplexität O (N log N) (und für kleine und sehr große große Arrays ist es näher an O (N)), wo Ihre Lösung mit verschachtelten Schleifen O (N ^ 2) ist.

Jedoch. Welche Datenstrukturen am effizientesten sind, hängt stark vom eigentlichen Problem ab.


-3

Sie können ein Wörterbuch verwenden, um die Leistung erheblich zu optimieren

Dies ist ein weiteres Beispiel:

locations = {}
for i in range(len(airports)):
    locations[airports["abb"][i][1:-1]] = (airports["height"][i], airports["width"][i])

for i in range(len(uniqueData)):
    h, w = locations[uniqueData["dept_apt"][i]]
    uniqueData["dept_apt_height"][i] = h
    uniqueData["dept_apt_width"][i] = w

2
Dies ist ganz klar immer noch die "for-loop" -Gedankenschule.
8bittree
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.