Domanda

Se ho un ciclo for che è nidificato all'interno di un altro, come posso uscire efficacemente da entrambi i loop (interno ed esterno) nel modo più rapido possibile?

Non voglio usare un booleano e poi devo dire andare a un altro metodo, ma piuttosto per eseguire la prima riga di codice dopo il ciclo esterno.

Qual è un modo rapido e piacevole per farlo?

Grazie


Stavo pensando che le eccezioni non sono economiche / dovrebbero essere gettate solo in una condizione davvero eccezionale, ecc. Quindi non penso che questa soluzione sarebbe buona dal punto di vista delle prestazioni.

Non credo sia giusto sfruttare le nuove funzionalità di .NET (metodi anon) per fare qualcosa che è piuttosto fondamentale.

Per questo motivo, tvon (mi dispiace non è possibile scrivere il nome utente completo!) ha una buona soluzione.

Marc: Bel uso dei metodi anon, e anche questo è fantastico, ma poiché potrei essere in un lavoro in cui non utilizziamo una versione di .NET / C # che supporti i metodi anon, ho bisogno di conoscere anche un approccio tradizionale .

È stato utile?

Soluzione

Bene, vai , ma è brutto e non sempre possibile. Puoi anche posizionare i loop in un metodo (o un metodo anon) e utilizzare return per tornare al codice principale.

    // 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");

Nota che in C # 7 dovremmo ottenere "funzioni locali", che (sintassi tbd ecc.) significa che dovrebbe funzionare in modo simile a:

// 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");

Altri suggerimenti

Adattamento C # dell'approccio spesso usato in C - imposta il valore della variabile del loop esterno al di fuori delle condizioni del loop (vale a dire, per il loop che usa la variabile int INT_MAX -1 è spesso una buona scelta):

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    
}

Come dice la nota nel codice, break non salterà magicamente alla prossima iterazione del ciclo esterno - quindi se hai codice al di fuori del ciclo interno questo approccio richiede più controlli. Prendi in considerazione altre soluzioni in tal caso.

Questo approccio funziona con per e mentre scorre ma non funziona per foreach . Nel caso di foreach non avrai accesso al codice all'enumeratore nascosto, quindi non puoi cambiarlo (e anche se IEnumerator non ne avesse alcuni " Metodo MoveToEnd ").

Ringraziamenti agli autori dei commenti incorporati:
i = INT_MAX - 1 suggerimento di Meta
per / foreach commento di ygoe .
IntMax corretto di jmbpiano
commento sul codice dopo il ciclo interno di blizpasta

Questa soluzione non si applica a C #

Per le persone che hanno trovato questa domanda tramite altre lingue, Javascript, Java e D consentono interruzioni etichettate e continua :

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

Utilizzare una protezione adeguata nell'anello esterno. Imposta la guardia nel circuito interno prima di rompere.

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
    }
}

O meglio ancora, astrarre il loop interno in un metodo ed uscire dal loop esterno quando restituisce false.

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

    .... some outer loop stuff

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

    ... more outer loop stuff
}

Non citarmi su questo, ma potresti usare vai come suggerito in MSDN. Esistono altre soluzioni, come l'inclusione di un flag che viene verificato in ogni iterazione di entrambi i loop. Finalmente potresti usare un'eccezione come soluzione davvero pesante al tuo problema.

GOTO:

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

Stato:

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
   }
}

Eccezione:

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 ) {}

È possibile eseguire il refactoring del ciclo nidificato per in un metodo privato? In questo modo potresti semplicemente "tornare" fuori dal metodo per uscire dal ciclo.

Mi sembra che alle persone non piaccia molto un'istruzione goto , quindi ho sentito il bisogno di raddrizzare un po '.

Credo che le "emozioni" che le persone provano riguardo a goto alla fine si riducono alla comprensione del codice e (idee sbagliate) sulle possibili implicazioni delle prestazioni. Prima di rispondere alla domanda, entrerò quindi in alcuni dettagli su come viene compilata.

Come tutti sappiamo, C # viene compilato in IL, che viene quindi compilato in assembler usando un compilatore SSA. Darò un po 'di spunti su come funziona tutto questo, quindi proverò a rispondere alla domanda stessa.

Da C # a IL

Per prima cosa abbiamo bisogno di un pezzo di codice C #. Cominciamo semplice:

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

Farò questo passo per passo per darti una buona idea di cosa succede sotto il cofano.

Prima traduzione: da foreach al ciclo for equivalente (Nota: sto usando un array qui, perché non voglio entrare nei dettagli di IDisposable - nel qual caso dovrei anche usare un IEnumerable):

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

Seconda traduzione: per e break è tradotto in un equivalente più semplice:

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

E terza traduzione (questo è l'equivalente del codice IL): cambiamo break e mentre in un ramo:

    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; 

Mentre il compilatore fa queste cose in un solo passaggio, ti dà un'idea del processo. Il codice IL che si evolve dal programma C # è la traduzione letterale dell'ultimo codice C #. Puoi vederlo qui: https://dotnetfiddle.net/QaiLRz (fai clic su "visualizza IL")

Ora, una cosa che hai osservato qui è che durante il processo, il codice diventa più complesso. Il modo più semplice per osservarlo è il fatto che avevamo bisogno di sempre più codice per realizzare la stessa cosa. Potresti anche sostenere che foreach , per , mentre e break sono in realtà le scorciatoie per goto , che è in parte vero.

Da IL a Assembler

Il compilatore .NET JIT è un compilatore SSA. Non entrerò in tutti i dettagli del modulo SSA qui e su come creare un compilatore ottimizzante, è troppo, ma posso dare una comprensione di base di ciò che accadrà. Per una comprensione più approfondita, è meglio iniziare a leggere sull'ottimizzazione dei compilatori (mi piace questo libro per una breve introduzione: http://ssabook.gforge.inria.fr/latest/book.pdf ) e LLVM (llvm.org).

Ogni compilatore ottimizzato si basa sul fatto che il codice è facile e segue schemi prevedibili . Nel caso dei loop FOR, utilizziamo la teoria dei grafi per analizzare i rami e quindi ottimizzare cose come i cicli nei nostri rami (ad esempio rami all'indietro).

Tuttavia, ora abbiamo filiali dirette per implementare i nostri loop. Come avrai intuito, questo è in realtà uno dei primi passi che la JIT sta per risolvere, in questo modo:

    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:
    // ...

Come puoi vedere, ora abbiamo un ramo all'indietro, che è il nostro piccolo anello. L'unica cosa ancora brutta qui è il ramo con cui siamo finiti a causa della nostra dichiarazione break . In alcuni casi, possiamo spostarlo allo stesso modo, ma in altri è lì per rimanere.

Quindi perché il compilatore fa questo? Bene, se possiamo srotolare il loop, potremmo essere in grado di vettorializzarlo. Potremmo anche essere in grado di provare che sono state aggiunte solo costanti, il che significa che tutto il nostro circuito potrebbe svanire nel nulla. Riassumendo: rendendo prevedibili gli schemi (rendendo prevedibili i rami), possiamo provare che certe condizioni rimangono nel nostro ciclo, il che significa che possiamo fare magie durante l'ottimizzazione JIT.

Tuttavia, i rami tendono a spezzare quei bei modelli prevedibili, il che è qualcosa di ottimizzatore quindi una specie di antipatia. Rompi, continua, andiamo - tutti intendono rompere questi schemi prevedibili - e quindi non sono davvero "carini".

A questo punto dovresti anche capire che un semplice foreach è più prevedibile di una serie di goto che vanno ovunque. In termini di (1) leggibilità e (2) dal punto di vista dell'ottimizzatore, è sia la soluzione migliore.

Un'altra cosa degna di nota è che è molto importante per l'ottimizzazione dei compilatori di assegnare i registri alle variabili (un processo chiamato allocazione dei registri ). Come forse saprai, c'è solo un numero finito di registri nella tua CPU e sono di gran lunga i pezzi di memoria più veloci nel tuo hardware. Le variabili utilizzate nel codice che si trova nel ciclo più interno, hanno maggiori probabilità di ottenere un registro assegnato, mentre le variabili al di fuori del ciclo sono meno importanti (perché questo codice è probabilmente colpito meno).

Aiuto, troppa complessità ... cosa devo fare?

La linea di fondo è che dovresti sempre usare i costrutti del linguaggio che hai a tua disposizione, che di solito costruiranno (implicitamente) modelli prevedibili per il tuo compilatore. Cerca di evitare rami strani se possibile (in particolare: break , continue , goto o return nel mezzo di Niente).

La buona notizia qui è che questi schemi prevedibili sono sia facili da leggere (per gli umani) che facili da individuare (per i compilatori).

Uno di questi schemi si chiama SESE, che sta per Single Entry Single Exit.

E ora arriviamo alla vera domanda.

Immagina di avere qualcosa del genere:

// a is a variable.

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

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

Il modo più semplice per rendere questo un modello prevedibile è semplicemente eliminare se completamente:

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

In altri casi puoi anche dividere il metodo in 2 metodi:

// 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;
}

Variabili temporanee? Buono, cattivo o brutto?

Potresti persino decidere di restituire un booleano all'interno del loop (ma preferisco personalmente il modulo SESE perché è così che il compilatore lo vedrà e penso che sia più pulito da leggere).

Alcune persone pensano che sia più pulito usare una variabile temporanea e propongono una soluzione come questa:

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.
  // ...
}
// ...

Personalmente sono contrario a questo approccio. Guarda di nuovo su come viene compilato il codice. Ora pensa a cosa farà con questi schemi piacevoli e prevedibili. Prendi la foto?

Bene, fammi precisare. Quello che accadrà è che:

  • Il compilatore scriverà tutto come rami.
  • Come fase di ottimizzazione, il compilatore eseguirà l'analisi del flusso di dati nel tentativo di rimuovere la strana variabile more utilizzata solo nel flusso di controllo.
  • In caso di successo, la variabile more verrà eliminata dal programma e restano solo i rami. Questi rami saranno ottimizzati, quindi otterrai un solo ramo dal ciclo interno.
  • In caso di esito negativo, la variabile più viene sicuramente utilizzata nel ciclo più interno, quindi se il compilatore non lo ottimizza, ha un'alta probabilità di essere allocato in un registro (che mangia preziosa memoria del registro).

Quindi, per riassumere: l'ottimizzatore nel tuo compilatore avrà un sacco di problemi per capire che altro viene usato solo per il flusso di controllo e nel migliore case case lo tradurrà in un singolo ramo esterno al ciclo for esterno.

In altre parole, lo scenario migliore è che finirà con l'equivalente di questo:

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:

// ...

La mia opinione personale su questo è abbastanza semplice: se questo è ciò che intendevamo da sempre, rendiamo il mondo più facile sia per il compilatore che per la leggibilità, e scriviamo subito.

tl; dr:

Linea di fondo:

  • Se possibile, usa una condizione semplice nel tuo ciclo for. Attieniti ai costrutti linguistici di alto livello che hai su yo

considera una funzione / metodo e usa il ritorno anticipato, oppure riorganizza i tuoi loop in una clausola while. vai / eccezioni / qualunque cosa non sia certamente appropriata qui.

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

Hai chiesto una combinazione di veloce, simpatico, nessun uso di un valore booleano, nessun uso di goto e C #. Hai escluso tutti i modi possibili di fare ciò che vuoi.

Il modo più rapido e meno brutto è usare un goto.

A volte è bello astrarre il codice nella sua stessa funzione e piuttosto che usare un ritorno in anticipo, anche se i rendimenti in anticipo sono malvagi:)

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;
            }
        }
    }
}

A seconda della situazione, potresti essere in grado di farlo, ma solo se non esegui il codice DOPO il ciclo interno.

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

Non è elegante, ma potrebbe essere la soluzione più semplice a seconda del problema.

Ho visto molti esempi che usano " break " ma nessuno che utilizza " continua " ;.

Richiederebbe comunque una bandiera di qualche tipo nel ciclo interno:

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 quando ho visto break in C un paio di decenni fa, questo problema mi ha irritato. Speravo che qualche miglioramento del linguaggio avrebbe avuto un'estensione da interrompere che avrebbe funzionato così:

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.

Ricordo dai miei giorni da studente che è stato detto che è matematicamente dimostrabile che puoi fare qualsiasi cosa nel codice senza un goto (cioè non esiste una situazione in cui goto è l'unica risposta). Quindi, non uso mai i goto (solo le mie preferenze personali, non suggerendo che ho ragione o torto)

Ad ogni modo, per uscire dai loop nidificati faccio qualcosa del genere:

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;
}

... spero che ciò che aiuta per quelli come me sia un fanbo "fanbo" " :)

È così che l'ho fatto. Ancora una soluzione alternativa.

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;
  }
}

Genera un'eccezione personalizzata che esce dal ciclo esterno.

Funziona con per , foreach o mentre o qualsiasi tipo di loop e qualsiasi linguaggio che utilizza prova a rilevare l'eccezione blocco

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
            }
        }

Come vedo, hai accettato la risposta in cui la persona ti rimanda alla dichiarazione goto, dove nella programmazione moderna e nell'opinione degli esperti goto è un killer, lo abbiamo chiamato un killer nella programmazione che ha alcune ragioni, di cui non parlerò qui a questo punto, ma la soluzione della tua domanda è molto semplice, puoi usare una bandiera booleana in questo tipo di scenario come lo dimostrerò nel mio esempio:

            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;
               }

semplice e chiaro. :)

Hai persino guardato la parola chiave break ? Ò.ò

Questo è solo uno pseudo-codice, ma dovresti essere in grado di vedere cosa intendo:

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

Se pensi che break sia una funzione come break () , allora il suo parametro sarebbe il numero di loop da cui uscire. Dato che siamo nel terzo ciclo del codice qui, possiamo uscire da tutti e tre.

Manuale: http://php.net/break

Penso che a meno che tu non voglia fare la cosa "booleana" l'unica soluzione è in realtà lanciare. Che ovviamente non dovresti fare ..!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top