Frage

Wenn ich eine for-Schleife, die in einem anderen verschachtelt ist, wie kann ich komme effizient von beiden Schleifen (innen und außen) in dem schnellstmöglichen Weg?

Ich will nicht einen boolean verwenden haben und müssen dann zu einer anderen Methode gehen sagen, sondern nur die erste Zeile des Codes nach der äußeren Schleife auszuführen.

Was ist eine schnelle und schöne Art und Weise über diese gehen?

Danke


Ich dachte, dass Ausnahmen sind nicht billig / sollte nur daher in einem wirklich außergewöhnlichen Zustand usw. geworfen Ich glaube nicht, diese Lösung aus Sicht der Leistung gut sein würde.

Ich fühle mich nicht es richtig ist, den Vorteil der neueren Features in .NET (Anon Methoden) zu nehmen, etwas zu tun, die ziemlich grundlegende Bedeutung ist.

Dieser Grund, tvon (sorry kann nicht voll Benutzername buchstabieren!) Hat eine schöne Lösung.

Marc: Nice Verwendung von Anon Methoden, und das ist zu groß, aber weil ich in einem Job sein könnte, wo wir keine Version von .NET / C # verwenden, die Anon Methoden unterstützt, ich brauche auch einen traditionellen Ansatz wissen .

War es hilfreich?

Lösung

Nun, goto, aber das ist hässlich, und nicht immer möglich. Sie können auch die Schlaufen in ein Verfahren platzieren (oder eine Anon-Methode) und verwenden return zum Hauptcode verlassen zurück.

    // goto
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            goto Foo; // yeuck!
        }
    }
Foo:
    Console.WriteLine("Hi");

vs:

// anon-method
Action work = delegate
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits anon-method
        }
    }
};
work(); // execute anon-method
Console.WriteLine("Hi");

Beachten Sie, dass 7 in C # sollten wir "lokale Funktionen", die (Syntax tbd usw.) bedeutet, erhalten sollte es so etwas wie arbeiten:

// local function (declared **inside** another method)
void Work()
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits local function
        }
    }
};
Work(); // execute local function
Console.WriteLine("Hi");

Andere Tipps

C # Anpassung der Ansatz verwendet, oft in C - eingestellt Wert der Variablen außerhalb der äußeren Schleife der Schleifenbedingungen (das heißt für Schleife int Variable INT_MAX -1 verwendet, ist oft eine gute Wahl):

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        if (exit_condition)
        {
            // cause the outer loop to break:
            // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
            i = int.MaxValue - 1;
            Console.WriteLine("Hi");
            // break the inner loop
            break;
        }
    }
    // if you have code in outer loop it will execute after break from inner loop    
}

Als Hinweis im Code sagt break nicht auf magische Weise zur nächsten Iteration der äußeren Schleife springen - wenn Sie also Code außerhalb der inneren Schleife haben diese Vorgehensweise mehr Kontrollen erforderlich. Betrachten wir andere Lösungen in einem solchen Fall.

Dieser Ansatz funktioniert mit for und while Schleifen funktioniert aber nicht für foreach. Im Falle von foreach werden Sie keinen Code Zugriff auf die versteckten enumerator haben, so dass Sie es nicht ändern können (und auch wenn Sie einige „MoveToEnd“ Methode nicht haben IEnumerator könnte).

Acknowledgements Kommentare der Autoren inlined:
i = INT_MAX - 1 Vorschlag von Meta
for / foreach Kommentar von ygoe .
Proper IntMax von jmbpiano
Bemerkung über Code nach innerer Schleife von blizpasta

Diese Lösung gilt nicht für C #

Für Menschen, die diese Frage über andere Sprachen gefunden, Javascript, Java und D ermöglichen markierte Pausen und weiter :

outer: while(fn1())
{
   while(fn2())
   {
     if(fn3()) continue outer;
     if(fn4()) break outer;
   }
}

Verwenden Sie eine geeignete Schutzvorrichtung in der äußeren Schleife. Stellen Sie die Wache in der inneren Schleife, bevor Sie brechen.

bool exitedInner = false;

for (int i = 0; i < N && !exitedInner; ++i) {

    .... some outer loop stuff

    for (int j = 0; j < M; ++j) {

        if (sometest) {
            exitedInner = true;
            break;
        }
    }
    if (!exitedInner) {
       ... more outer loop stuff
    }
}

Oder noch besser, abstrakt die innere Schleife in ein Verfahren und verlassen die äußere Schleife, wenn es false zurück.

for (int i = 0; i < N; ++i) {

    .... some outer loop stuff

    if (!doInner(i, N, M)) {
       break;
    }

    ... more outer loop stuff
}

zitieren Sie mich nicht auf diese, aber man konnte a href verwenden <= "http://msdn.microsoft.com/es-es/library/13940fs2(VS.80).aspx" rel = "noreferrer" > goto wie in der MSDN vorgeschlagen. Es gibt andere Lösungen, wie auch einen Flag, das in jeder Iteration der beiden Schleifen überprüft wird. Schließlich könnte man eine Ausnahme als wirklich Schwergewichts-Lösung für Ihr Problem verwenden.

GOTO:

for ( int i = 0; i < 10; ++i ) {
   for ( int j = 0; j < 10; ++j ) {
      // code
      if ( break_condition ) goto End;
      // more code
   }
}
End: ;

Bedingung:

bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
   for ( int j = 0; j < 10 && !exit; ++j ) {
      // code
      if ( break_condition ) {
         exit = true;
         break; // or continue
      }
      // more code
   }
}

Ausnahme:

try {
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             throw new Exception()
          }
          // more code
       }
    }
catch ( Exception e ) {}

Ist es möglich, die verschachtelte for-Schleife in eine private Methode Refactoring? Auf diese Weise können Sie einfach ‚Rückkehr‘ aus dem Verfahren um die Schleife zu beenden.

Es scheint mir, wie die Menschen eine goto Aussage viel Abneigung, so fühlte ich die Notwendigkeit, dies ein wenig zu begradigen.

schließlich lässt sich auf das Verständnis von Code und (Missverständnisse) über mögliche Auswirkungen auf die Leistung

Ich glaube, die Emotionen "Menschen über goto haben. Vor der Beantwortung der Frage, werde ich deshalb geht zunächst in einige der Details, wie es kompiliert wird.

Wie wir alle wissen, ist C # IL zusammengestellt, die dann einen SSA-Compiler Assembler kompiliert wird, verwendet wird. Ich werde ein wenig Erkenntnisse darüber, wie das alles funktioniert geben, und dann versuchen, die Frage selbst zu beantworten.

Von C # IL

Zuerst müssen wir ein Stück C # -Code. Beginnen wir einfach:

foreach (var item in array)
{
    // ... 
    break;
    // ...
}

Ich werde diesen Schritt für Schritt Sie eine gute Vorstellung davon zu geben, was unter der Haube passiert.

Erste Übersetzung: von foreach der äquivalenten for Schleife (Anmerkung: Ich verwende eine Reihe hier, weil ich will nicht in Details von IDisposable erhalten - in diesem Fall würde ich muß auch ein IEnumerable verwenden ):

for (int i=0; i<array.Length; ++i)
{
    var item = array[i];
    // ...
    break;
    // ...
}

Zweite Übersetzung: die for und break in eine einfachen äquivalent übersetzt:

int i=0;
while (i < array.Length)
{
    var item = array[i];
    // ...
    break;
    // ...
    ++i;
}

Und drittens Übersetzung (dies entspricht der IL-Code): Wir ändern break und while in einen Zweig:

    int i=0; // for initialization

startLoop:
    if (i >= array.Length) // for condition
    {
        goto exitLoop;
    }
    var item = array[i];
    // ...
    goto exitLoop; // break
    // ...
    ++i;           // for post-expression
    goto startLoop; 

Während die Compiler diese Dinge in einem einzigen Schritt tun, es gibt Ihnen Einblick in den Prozess. Der IL-Code, der von dem C # Programm entwickelt ist die wörtliche Übersetzung der letzten C # -Code. Sie können selbst sehen hier: https://dotnetfiddle.net/QaiLRz (klicken Sie auf 'Ansicht IL')

Nun, eine Sache, die Sie hier beobachtet haben, ist während des Prozesses, dass der Code komplexer wird. Der einfachste Weg, dies zu beobachten, ist die Tatsache, dass wir mehr und mehr Code benötigt, um die gleiche Sache ackomplish. Man könnte auch argumentieren, dass foreach, for, while und break sind tatsächlich kurz Hände für goto, die zum Teil wahr ist.

Von IL Assembler

Der .NET JIT-Compiler ist ein SSA-Compiler. Ich werde nicht alle in den Details der SSA-Form gehe hier und wie ein optimierenden Compiler erstellen, es ist einfach zu viel, kann aber ein grundlegendes Verständnis über das, was passieren wird. Für ein tieferes Verständnis, ist es am besten auf der Optimierung der Compiler Lese zu starten (I wie dieses Buch zu tun für eine kurze Einführung: http://ssabook.gforge.inria.fr/latest/book.pdf ) und LLVM (llvm.org).

Jede Optimierung der Compiler beruht auf der Tatsache, dass Code ist leicht und folgt vorhersagbaren Mustern . Im Fall von FOR-Schleifen verwenden wir Graphentheorie Zweige zu analysieren und dann optimieren Dinge wie cycli in unseren Filialen (zum Beispiel Zweige rückwärts).

Allerdings haben wir jetzt nach vorne Zweige unsere Schleifen zu implementieren. Wie Sie vielleicht schon erraten haben, das ist eigentlich einer der ersten Schritte ist der JIT beheben wird, wie folgt aus:

    int i=0; // for initialization

    if (i >= array.Length) // for condition
    {
        goto endOfLoop;
    }

startLoop:
    var item = array[i];
    // ...
    goto endOfLoop; // break
    // ...
    ++i;           // for post-expression

    if (i >= array.Length) // for condition
    {
        goto startLoop;
    }

endOfLoop:
    // ...

Wie Sie sehen können, haben wir jetzt eine Rückwärtsverzweigung, die unsere kleine Schleife ist. Das einzige, was hier noch böse ist, ist der Zweig, der wir mit aufgrund unserer break Aussage endete. In einigen Fällen können wir dies auf die gleiche Art und Weise bewegen, aber es ist es in anderen zu bleiben.

Also warum die Compiler dies tun? Nun, wenn wir die Schleife abrollen können, könnten wir in der Lage sein, es zu vektorisieren. Wir könnten sogar den Nachweis der Lage sein, dass es nur hinzugefügt Konstanten sind, die unsere ganze Schleife bedeutet, in Luft auflösen könnte. Fassen wir zusammen:. Durch die Muster vorhersehbar machen (durch die Zweige vorhersehbar zu machen), können wir Beweis, dass bestimmte Bedingungen in unserer Schleife halten, was bedeutet, dass wir Magie während der JIT-Optimierung tun

Allerdings Zweige neigen jene schön zu brechenvorhersehbare Muster, die deshalb etwas Optimizern sind Art-Abneigung. Pause, weiter, gehe zu -. Sie alle beabsichtigen, diese vorhersehbare Schnittmuster- zu brechen und sind daher nicht wirklich ‚schön‘

Sie sollten auch an dieser Stelle erkennen, dass eine einfache foreach berechenbarer ist dann ein Bündel von goto Aussagen, die alle über den Ort gehen. Im Hinblick auf (1) die Lesbarkeit und (2) von einer Optimierer Perspektive, ist es sowohl die bessere Lösung.

Eine andere Sache, erwähnenswert ist, dass es sehr wichtig ist Compiler zur Optimierung Register Variablen zuweisen (ein Prozess namens registrieren Zuordnung ). Wie Sie vielleicht wissen, gibt es nur eine endliche Anzahl von Registern in der CPU und sie sind mit Abstand die schnellsten Stücke Speicher in Ihrer Hardware. Variablen im Code verwendet, die in der am weitesten innen Schleife ist, ist eher ein Register zugewiesen zu bekommen, während Variablen außerhalb der Schleife weniger wichtig ist (weil dieser Code wahrscheinlich weniger getroffen wird).

Hilfe, zu viel Komplexität ... was soll ich tun?

Das Endergebnis ist, dass Sie immer die Sprache verwenden soll Konstrukte Sie zur Verfügung haben, die in der Regel (implizit) vorhersehbare Muster für Ihren Compiler bauen. Versuchen Sie zu vermeiden seltsame Zweige, wenn möglich (speziell: break, continue, goto oder ein return in der Mitte nichts).

Die gute Nachricht dabei ist, dass diese vorhersehbaren Muster sowohl leicht zu lesen sind (für die Menschen) und leicht zu erkennen (für Compiler).

Eines dieser Muster ist SESE genannt, die für die Einzelerfassung Einzel Ausgang steht.

Und nun kommen wir zur eigentlichen Frage.

Stellen Sie sich vor, dass Sie etwas davon haben:

// a is a variable.

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...

     if (i*j > a) 
     {
        // break everything
     }
  }
}

Der einfachste Weg, dies zu einem vorhersagbaren Muster zu machen, ist, einfach die if vollständig zu eliminieren:

int i, j;
for (i=0; i<100 && i*j <= a; ++i) 
{
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
}

In anderen Fällen können Sie spalten auch die Methode in zwei Methoden:

// Outer loop in method 1:

for (i=0; i<100 && processInner(i); ++i) 
{
}

private bool processInner(int i)
{
  int j;
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
  return i*j<=a;
}

Temporäre Variablen? Gut, schlecht oder hässlich?

Sie können sogar entscheiden, ein boolean Rückkehr aus der Schleife (aber ich persönlich bevorzuge die SESE Form, weil das ist, wie die Compiler es sehen und ich denke, es sauberer ist zu lesen).

Einige Leute denken, es sauberer ist eine temporäre Variable zu verwenden, und eine Lösung wie diese schlagen vor:

bool more = true;
for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j) 
  {
     // ...
     if (i*j > a) { more = false; break; } // yuck.
     // ...
  }
  if (!more) { break; } // yuck.
  // ...
}
// ...

Ich bin persönlich entgegengesetzt diesen Ansatz. Schauen Sie noch einmal auf, wie der Code kompiliert wird. Jetzt darüber nachdenken, was dies mit diesen netten, vorhersagbaren Mustern zu tun. Verstehe?

Rechts, lassen Sie mich es buchstabieren. Was passiert, ist, dass:

  • Der Compiler wird alles als Zweig schreiben.
  • Als Optimierungsschritt wird die Compiler Datenflussanalyse in einem Versuch, tut den seltsamen more Variable zu entfernen, das geschieht nur in Steuerfluss verwendet werden.
  • Falls erfolgreich, wird der Variable more aus dem Programm eliminiert werden und nur Zweige bleiben. Diese Zweige werden optimiert, so dass Sie nur einen einzigen Zweig aus der inneren Schleife bekommen.
  • Wenn unsuccesful die Variable more definitiv in den Innen meist Schleifen verwendet wird, so dass, wenn die Compiler es nicht weg optimieren, hat es eine hohe Wahrscheinlichkeit, ein Register zugewiesen werden (die Speicher wertvolle Register frisst).

Also, zusammenfassen: das Optimierungsprogramm in Ihrem Compiler wird eine verdammt viel Mühe geht in, um herauszufinden, dass more nur für den Steuerfluss verwendet wird, und im besten Fall übersetzen, um es zu einem einzigen Zweig außerhalb der äußeren for-Schleife.

Mit anderen Worten, der beste Fall ist, dass es mit dem Äquivalent von diesem enden wird:

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...
     if (i*j > a) { goto exitLoop; } // perhaps add a comment
     // ...
  }
  // ...
}
exitLoop:

// ...

Meine persönliche Meinung dazu ist ganz einfach: Wenn das ist, was wir die ganze Zeit gedacht, lassen Sie uns die Welt erleichtern sowohl den Compiler und die Lesbarkeit und schreiben, dass Recht away.

tl; dr:

Fazit:

  • Verwenden Sie eine einfache Bedingung in Ihrem for-Schleife, wenn möglich. Halten Sie sich an die High-Level-Sprachkonstrukte Sie zur Verfügung haben, so viel wie möglich.
  • Wenn alles fehlschlägt und Sie sind entweder mit goto oder bool more links, bevorzugen die ehemalige.

Faktor in eine Funktion / Methode und verwenden frühe Rückkehr oder Loops in eine while-Klausel ändern. goto / Ausnahmen / was auch immer hier sicherlich nicht geeignet sind.

def do_until_equal():
  foreach a:
    foreach b:
      if a==b: return

Sie fragte nach einer Kombination von schnellen, schön, ohne Verwendung eines boolean, ohne Verwendung von goto, und C #. Sie haben alle Möglichkeiten zu tun, ausgeschlossen, was Sie wollen.

Die schnelle und am wenigsten hässliche Art und Weise ist eine goto zu verwenden.

Manchmal schön zu abstrahieren den Code in seine eigene Funktion und als eine frühe Rückkehr verwenden - frühe Rückkehr sind böse aber:)

public void GetIndexOf(Transform transform, out int outX, out int outY)
{
    outX = -1;
    outY = -1;

    for (int x = 0; x < Columns.Length; x++)
    {
        var column = Columns[x];

        for (int y = 0; y < column.Transforms.Length; y++)
        {
            if(column.Transforms[y] == transform)
            {
                outX = x;
                outY = y;

                return;
            }
        }
    }
}

Je nach Ihrer Situation, können Sie in der Lage sein, dies zu tun, aber nur, wenn Ihr nicht Code nach der inneren Schleife ausgeführt wird.

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        i = 100;
        break;
    }
}

Es ist nicht elegent, aber es kann die einfachste Lösung für Ihr Problem abhängig sein.

Ich habe viele Beispiele gesehen, die „Pause“, aber keine verwenden, die auf „weiter“ verwenden.

Es wäre noch eine Fahne von irgendeiner Art in der inneren Schleife erfordern:

while( some_condition )
{
    // outer loop stuff
    ...

    bool get_out = false;
    for(...)
    {
        // inner loop stuff
        ...

        get_out = true;
        break;
    }

    if( get_out )
    {
        some_condition=false;
        continue;
    }

    // more out loop stuff
    ...

}

Da ich zum ersten Mal break in C ein paar Jahrzehnte zurück sah, wurde dieses Problem mich geärgert. Ich war einige Sprachverbesserung der Hoffnung, würde eine Verlängerung haben zu brechen, die so funktionieren würde:

break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it's parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.

Ich erinnere mich aus meiner Studentenzeit, dass es gesagt wurde, es mathematisch beweisbar ist, dass Sie alles in Code ohne goto tun können (das heißt, es gibt keine Situation, wo ginge die einzige Antwort ist). Also, ich benutze nie GOTOs (nur meine persönliche Präferenz, nicht was darauf hindeutet, dass ich richtig oder falsch)

Wie auch immer, brechen aus verschachtelten Schleifen ich etwas wie folgt aus:

var isDone = false;
for (var x in collectionX) {
    for (var y in collectionY) {
        for (var z in collectionZ) {
            if (conditionMet) {
                // some code
                isDone = true;
            }
            if (isDone)
                break;
        }
        if (isDone) 
            break;
    }
    if (isDone)
        break;
}

... ich hoffe, das hilft für diejenigen, die wie ich sind anti-goto "Fanboys":)

Das ist, wie ich es tat. Noch eine Abhilfe.

foreach (var substring in substrings) {
  //To be used to break from 1st loop.
  int breaker=1;
  foreach (char c in substring) {
    if (char.IsLetter(c)) {
      Console.WriteLine(line.IndexOf(c));
      \\setting condition to break from 1st loop.
      breaker=9;
      break;
    }
  }
  if (breaker==9) {
    break;
  }
}

eine benutzerdefinierte Ausnahme auslösen, die outter Schleife geht aus.

Es funktioniert für for, foreach oder while oder jede Art von Schleife und jede Sprache, die try catch exception Block

verwendet
try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}
         bool breakInnerLoop=false
        for(int i=0;i<=10;i++)
        {
          for(int J=0;i<=10;i++)
          {
              if(i<=j)
                {
                    breakInnerLoop=true;
                    break;
                }
          }
            if(breakInnerLoop)
            {
            continue
            }
        }

Wie ich sehe Sie die Antwort akzeptiert, in der die Person, die Sie goto-Anweisung bezieht, wo in der modernen Programmierung und in Gutachten gehe ist ein Killer, riefen wir es ein Killer in der Programmierung, die einige bestimmte Gründe haben, die ich diskutieren nicht es hier an dieser Stelle vorbei, aber die Lösung Ihrer Frage ist sehr einfach, Sie eine Boolesche Flagge in dieser Art von Szenario verwenden können, wie ich es in meinem Beispiel demonstrieren:

            for (; j < 10; j++)
            {
                //solution
                bool breakme = false;
                for (int k = 1; k < 10; k++)
                {
                   //place the condition where you want to stop it
                    if ()
                    {
                        breakme = true;
                        break;
                    }
                }

                if(breakme)
                    break;
               }

einfach und schlicht. :)

Hast du dir auch bei dem break Stichwort? Ò.ó

Dies ist nur Pseudo-Code, aber Sie sollten in der Lage sein zu sehen, was ich meine:

<?php
for(...) {
    while(...) {
        foreach(...) {
            break 3;
        }
    }
}

Wenn Sie denken über break eine Funktion wie break() zu sein, dann wäre es Parameter die Anzahl der Schleifen brechen aus. Wie wir bereits in der dritten Schleife im Code hier sind, können wir aus allen drei brechen.

Handbuch: http://php.net/break

Ich denke, wenn Sie die „boolean Sache“ die einzige Lösung wollen, ist eigentlich zu werfen. Sie sollten es natürlich nicht tun ..!

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top