Wenn ich eine for-Schleife habe, die in einer anderen verschachtelt ist, wie kann ich dann schnellstmöglich effizient aus beiden Schleifen (innen und außen) herauskommen?
Ich möchte keinen Booleschen Wert verwenden und dann "Gehe zu einer anderen Methode" sagen müssen, sondern nur die erste Codezeile nach der äußeren Schleife ausführen.
Was ist eine schnelle und nette Vorgehensweise?
Ich dachte, dass Ausnahmen nicht billig sind / nur in einem wirklich außergewöhnlichen Zustand usw. geworfen werden sollten. Daher denke ich nicht, dass diese Lösung aus Sicht der Leistung gut wäre.
Ich halte es nicht für richtig, die neueren Funktionen in .NET (anon-Methoden) zu nutzen, um etwas zu tun, das ziemlich grundlegend ist.
c#
for-loop
nested-loops
GurdeepS
quelle
quelle
Antworten:
Nun,
goto
aber das ist hässlich und nicht immer möglich. Sie können die Schleifen auch in eine Methode (oder eine anon-Methode)return
einfügen und verwenden, um zum Hauptcode zurückzukehren.vs:
Beachten Sie, dass wir in C # 7 "lokale Funktionen" erhalten sollten, was (Syntax tbd usw.) bedeutet, dass es ungefähr so funktionieren sollte:
quelle
goto
es per se schädlich ist , während es einfach das richtige Instrument ist, um einige Dinge zu tun, und wie viele Instrumente möglicherweise missbraucht werden. Diese religiöse Abneigung gegengoto
ist ehrlich gesagt ziemlich dumm und definitiv unwissenschaftlich.C # -Anpassung des Ansatzes, der häufig in C verwendet wird - Wert der Variablen der äußeren Schleife außerhalb der Schleifenbedingungen einstellen (dh für Schleife mit int-Variable
INT_MAX -1
ist häufig eine gute Wahl):Wie im Code angegeben,
break
wird nicht auf magische Weise zur nächsten Iteration der äußeren Schleife gesprungen. Wenn Sie also Code außerhalb der inneren Schleife haben, erfordert dieser Ansatz mehr Überprüfungen. Betrachten Sie in diesem Fall andere Lösungen.Dieser Ansatz funktioniert mit
for
undwhile
Schleifen, funktioniert aber nicht fürforeach
. Fallsforeach
Sie keinenIEnumerator
Codezugriff auf den versteckten Enumerator haben, können Sie ihn nicht ändern (und selbst wenn Sie keine "MoveToEnd" -Methode haben könnten).Danksagung an die Autoren
i = INT_MAX - 1
von Inline-Kommentaren: Vorschlag von Metafor
/foreach
Kommentar von ygoe .Richtig
IntMax
von jmbpianoBemerkung über Code nach innerer Schleife von blizpasta
quelle
Diese Lösung gilt nicht für C #
Für Personen, die diese Frage über andere Sprachen gefunden haben, ermöglichen Javascript, Java und D gekennzeichnete Pausen und fahren fort :
quelle
Verwenden Sie einen geeigneten Schutz in der äußeren Schlaufe. Setzen Sie den Schutz in die innere Schlaufe, bevor Sie brechen.
Oder noch besser, abstrahieren Sie die innere Schleife in eine Methode und verlassen Sie die äußere Schleife, wenn sie false zurückgibt.
quelle
Zitiere mich nicht dazu, aber du könntest goto gebrauchen wie im MSDN vorgeschlagen. Es gibt andere Lösungen, z. B. ein Flag, das in jeder Iteration beider Schleifen überprüft wird. Schließlich könnten Sie eine Ausnahme als wirklich schwere Lösung für Ihr Problem verwenden.
GEHE ZU:
Bedingung:
Ausnahme:
quelle
Ist es möglich, die verschachtelte for-Schleife in eine private Methode umzuwandeln? Auf diese Weise können Sie einfach aus der Methode "zurückkehren", um die Schleife zu verlassen.
quelle
[&] { ... return; ... }();
Es scheint mir, als ob die Leute eine
goto
Aussage nicht mögen , deshalb hatte ich das Bedürfnis, dies ein wenig zu korrigieren.Ich glaube, die "Emotionen", die Menschen haben, laufen
goto
letztendlich darauf hinaus, Code zu verstehen und (falsche Vorstellungen) über mögliche Auswirkungen auf die Leistung. Bevor ich die Frage beantworte, werde ich daher zunächst einige Details zur Kompilierung erläutern.Wie wir alle wissen, wird C # zu IL kompiliert, das dann mit einem SSA-Compiler zu Assembler kompiliert wird. Ich werde ein paar Einblicke geben, wie das alles funktioniert, und dann versuchen, die Frage selbst zu beantworten.
Von C # nach IL
Zuerst brauchen wir ein Stück C # -Code. Fangen wir einfach an:
Ich werde dies Schritt für Schritt tun, um Ihnen eine gute Vorstellung davon zu geben, was unter der Haube passiert.
Erste Übersetzung: von
foreach
in die äquivalentefor
Schleife (Hinweis: Ich verwende hier ein Array, da ich nicht auf Details von IDisposable eingehen möchte - in diesem Fall müsste ich auch eine IEnumerable verwenden):Zweite Übersetzung: das
for
undbreak
wird in ein einfacheres Äquivalent übersetzt:Und dritte Übersetzung (dies entspricht dem IL-Code): Wir ändern uns
break
undwhile
in einen Zweig:Während der Compiler diese Aufgaben in einem einzigen Schritt ausführt, erhalten Sie einen Einblick in den Prozess. Der IL-Code, der sich aus dem C # -Programm entwickelt, ist die wörtliche Übersetzung des letzten C # -Codes. Sie können sich hier selbst davon überzeugen: https://dotnetfiddle.net/QaiLRz (klicken Sie auf 'IL anzeigen')
Eine Sache, die Sie hier beobachtet haben, ist, dass der Code während des Prozesses komplexer wird. Der einfachste Weg, dies zu beobachten, ist die Tatsache, dass wir immer mehr Code benötigten, um dasselbe zu erreichen. Man könnte auch argumentieren , dass
foreach
,for
,while
undbreak
sind eigentlich Kurz Händegoto
, die zum Teil wahr ist.Von IL zu Assembler
Der .NET JIT-Compiler ist ein SSA-Compiler. Ich werde hier nicht auf alle Details des SSA-Formulars eingehen und darauf, wie ein optimierender Compiler erstellt wird. Es ist einfach zu viel, kann aber ein grundlegendes Verständnis darüber vermitteln, was passieren wird. Für ein tieferes Verständnis ist es am besten, sich mit der Optimierung von Compilern zu befassen (ich mag dieses Buch für eine kurze Einführung: http://ssabook.gforge.inria.fr/latest/book.pdf befassen ) und LLVM (llvm.org) .
Jeder optimierende Compiler verlässt sich auf die Tatsache, dass Code einfach ist und vorhersehbaren Mustern folgt . Im Fall von FOR-Schleifen verwenden wir die Graphentheorie, um Zweige zu analysieren und dann Dinge wie Zyklen in unseren Zweigen zu optimieren (z. B. Zweige rückwärts).
Wir haben jetzt jedoch Forward-Zweige, um unsere Schleifen zu implementieren. Wie Sie vielleicht vermutet haben, ist dies tatsächlich einer der ersten Schritte, die die JIT wie folgt beheben wird:
Wie Sie sehen können, haben wir jetzt einen Rückwärtszweig, der unsere kleine Schleife ist. Das einzige, was hier noch böse ist, ist der Zweig, mit dem wir aufgrund unserer
break
Aussage gelandet sind. In einigen Fällen können wir dies auf die gleiche Weise verschieben, in anderen bleibt es jedoch bestehen.Warum macht der Compiler das? Wenn wir die Schleife abrollen können, können wir sie möglicherweise vektorisieren. Wir könnten sogar beweisen können, dass nur Konstanten hinzugefügt werden, was bedeutet, dass unsere gesamte Schleife in Luft aufgehen könnte. Zusammenfassend lässt sich sagen: Indem wir die Muster vorhersehbar machen (indem wir die Zweige vorhersehbar machen), können wir beweisen, dass bestimmte Bedingungen in unserer Schleife gelten, was bedeutet, dass wir während der JIT-Optimierung zaubern können.
Zweige neigen jedoch dazu, diese schönen vorhersehbaren Muster zu brechen, was Optimierer daher nicht mögen. Brechen Sie, fahren Sie fort, gehen Sie - sie alle beabsichtigen, diese vorhersehbaren Muster zu brechen - und sind daher nicht wirklich "nett".
Sie sollten an dieser Stelle auch erkennen, dass eine einfache Aussage
foreach
vorhersehbarer ist als eine Reihe vongoto
Aussagen, die überall vorkommen. In Bezug auf (1) Lesbarkeit und (2) aus Sicht des Optimierers ist dies die bessere Lösung.Eine weitere erwähnenswerte Sache ist, dass es für die Optimierung von Compilern sehr relevant ist, Register Variablen zuzuweisen (ein Prozess, der als Registerzuweisung bezeichnet wird ). Wie Sie vielleicht wissen, gibt es in Ihrer CPU nur eine begrenzte Anzahl von Registern, und diese sind bei weitem die schnellsten Speicher in Ihrer Hardware. Variablen, die in Code verwendet werden, der sich in der innersten Schleife befindet, erhalten mit größerer Wahrscheinlichkeit ein Register, während Variablen außerhalb Ihrer Schleife weniger wichtig sind (da dieser Code wahrscheinlich weniger getroffen wird).
Hilfe, zu viel Komplexität ... was soll ich tun?
Das Fazit ist, dass Sie immer die Sprachkonstrukte verwenden sollten, die Ihnen zur Verfügung stehen, wodurch normalerweise (implizit) vorhersehbare Muster für Ihren Compiler erstellt werden. Versuchen Sie , seltsame Zweige wenn möglich zu vermeiden (insbesondere:
break
,continue
,goto
oder einreturn
in der Mitte nichts).Die gute Nachricht hier ist, dass diese vorhersehbaren Muster sowohl leicht zu lesen (für Menschen) als auch leicht zu erkennen (für Compiler) sind.
Eines dieser Muster heißt SESE und steht für Single Entry Single Exit.
Und jetzt kommen wir zur eigentlichen Frage.
Stellen Sie sich vor, Sie haben so etwas:
Der einfachste Weg, dies zu einem vorhersehbaren Muster zu machen, besteht darin, einfach Folgendes
if
vollständig zu eliminieren :In anderen Fällen können Sie die Methode auch in zwei Methoden aufteilen:
Temporäre Variablen? Gut, schlecht oder hässlich?
Sie könnten sogar beschließen, einen Booleschen Wert aus der Schleife zurückzugeben (aber ich persönlich bevorzuge das SESE-Formular, da der Compiler es so sieht und ich denke, es ist sauberer zu lesen).
Einige Leute halten es für sauberer, eine temporäre Variable zu verwenden, und schlagen eine Lösung wie diese vor:
Ich persönlich bin gegen diesen Ansatz. Sehen Sie sich noch einmal an, wie der Code kompiliert wird. Überlegen Sie nun, was dies mit diesen schönen, vorhersehbaren Mustern bewirken wird. Verstehe?
Richtig, lassen Sie es mich formulieren. Was passieren wird ist das:
more
Variable zu entfernen, die nur im Kontrollfluss verwendet wird.more
aus dem Programm entfernt und es bleiben nur Verzweigungen übrig. Diese Zweige werden optimiert, sodass Sie nur einen einzigen Zweig aus der inneren Schleife erhalten.more
definitiv in der innersten Schleife verwendet. Wenn der Compiler sie also nicht optimiert, besteht eine hohe Wahrscheinlichkeit, dass sie einem Register zugewiesen wird (das wertvollen Registerspeicher verbraucht).Zusammenfassend lässt sich sagen, dass der Optimierer in Ihrem Compiler eine Menge Probleme haben wird, um herauszufinden, dass er
more
nur für den Kontrollfluss verwendet wird, und ihn im besten Fall in einen einzelnen Zweig außerhalb des äußeren für übersetzt Schleife.Mit anderen Worten, das beste Szenario ist, dass es das Äquivalent dazu ergibt:
Meine persönliche Meinung dazu ist ganz einfach: Wenn wir dies die ganze Zeit beabsichtigt haben, lassen Sie uns die Welt sowohl für den Compiler als auch für die Lesbarkeit einfacher machen und das sofort schreiben.
tl; dr:
Endeffekt:
goto
oder bleibenbool more
, bevorzugen Sie das erstere.quelle
yield return
. Das heißt, obwohl es leicht zu zeigen ist, dass es einige nützliche Fälle gibt, ist es für die meisten Codes auf "Anwendungsebene" wahrscheinlich ein sehr geringes Auftreten von Frustration mit C #, mit Ausnahme derjenigen, die "nur von" C / C ++ kommen ;-) Ich vermisse es auch gelegentlich Rubys "Wurf" (Nicht-Ausnahme-Abwickeln) gelegentlich, da er auch in diese Domäne passt.goto
, macht Ihren Code nicht lesbarer - ich würde argumentieren, was genau das Gegenteil passiert: Tatsächlich wird Ihr Kontrollfluss mehr Knoten enthalten - was ziemlich genau ist die Definition von Komplexität. Nur zu vermeiden, um die Selbstdisziplin zu unterstützen, klingt in Bezug auf die Lesbarkeit kontraproduktiv.goto
einfach nicht die Disziplin haben, es nicht zu missbrauchen.Berücksichtigen Sie eine Funktion / Methode und verwenden Sie die frühe Rückgabe oder ordnen Sie Ihre Schleifen in eine while-Klausel um. goto / Ausnahmen / was auch immer sind hier sicherlich nicht angebracht.
quelle
Sie haben nach einer Kombination aus schnell, nett, ohne Verwendung eines Booleschen Werts, ohne Verwendung von goto und C # gefragt. Sie haben alle Möglichkeiten ausgeschlossen, das zu tun, was Sie wollen.
Der schnellste und am wenigsten hässliche Weg ist, ein goto zu verwenden.
quelle
Manchmal ist es schön, den Code in seine eigene Funktion zu abstrahieren und dann eine frühe Rückkehr zu verwenden - frühe Rückkehr ist jedoch böse :)
quelle
Ich habe viele Beispiele gesehen, die "break" verwenden, aber keine, die "continue" verwenden.
Es würde immer noch eine Art Flag in der inneren Schleife erfordern:
quelle
Seit ich vor
break
ein paar Jahrzehnten zum ersten Mal in C gesehen habe, hat mich dieses Problem geärgert. Ich hatte gehofft, dass eine Sprachverbesserung eine Erweiterung zum Brechen haben würde, die so funktionieren würde:quelle
Ich erinnere mich aus meiner Studienzeit, dass gesagt wurde, es sei mathematisch beweisbar, dass man alles im Code ohne goto machen kann (dh es gibt keine Situation, in der goto die einzige Antwort ist). Also benutze ich niemals goto's (nur meine persönliche Präferenz, was nicht bedeutet, dass ich richtig oder falsch bin)
Um aus verschachtelten Schleifen auszubrechen, mache ich ungefähr so:
... ich hoffe das hilft für diejenigen, die mich mögen, sind Anti-Goto "Fanboys" :)
quelle
So habe ich es gemacht. Immer noch eine Problemumgehung.
quelle
Der einfachste Weg, eine Doppelschleife zu beenden, wäre das direkte Beenden der ersten Schleife
quelle
Schleifen können unter Verwendung benutzerdefinierter Bedingungen in der Schleife unterbrochen werden, um sauberen Code zu erhalten.
Mit diesem Code erhalten wir die folgende Ausgabe:
quelle
Eine andere Option ist eine selbst aufgerufene anonyme Funktion . Kein goto, keine Bezeichnung, keine neue Variable, kein neuer Funktionsname und eine Zeile kürzer als das Beispiel der anonymen Methode oben.
quelle
Wirf eine benutzerdefinierte Ausnahme aus, die eine Oterter-Schleife auslöst.
Es funktioniert für
for
,foreach
oderwhile
oder irgendeine Art von Schleife und jede Sprache , dass Anwendungentry catch exception
blockierenquelle
quelle
Wie ich sehe, haben Sie die Antwort akzeptiert, in der die Person auf Ihre Aussage verweist, wo in der modernen Programmierung und in der Expertenmeinung goto ein Killer ist, haben wir es einen Killer in der Programmierung genannt, der einige bestimmte Gründe hat, über die ich hier nicht sprechen werde An diesem Punkt, aber die Lösung Ihrer Frage ist sehr einfach, können Sie in einem solchen Szenario ein Boolesches Flag verwenden, wie ich es in meinem Beispiel demonstrieren werde:
einfach und schlicht. :) :)
quelle
Hast du dir das
break
Schlüsselwort überhaupt angesehen? OoDies ist nur Pseudocode, aber Sie sollten sehen können, was ich meine:
Wenn Sie darüber nachdenken
break
, eine Funktion wie zu seinbreak()
, ist der Parameter die Anzahl der Schleifen, aus denen Sie ausbrechen müssen. Da wir uns hier in der dritten Schleife des Codes befinden, können wir aus allen drei ausbrechen.Handbuch: http://php.net/break
quelle
Ich denke, wenn Sie nicht das "Boolesche Ding" machen wollen, ist die einzige Lösung tatsächlich zu werfen. Was du natürlich nicht tun solltest ..!
quelle