Qual è il modo migliore per eseguire un ciclo all'indietro in C / C # / C ++?


101

Devo spostarmi all'indietro attraverso un array, quindi ho un codice come questo:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

C'è un modo migliore per farlo?

Aggiornamento: speravo che forse C # avesse un meccanismo integrato per questo come:

foreachbackwards (int i in myArray)
{
    // so easy
}

Aggiornamento 2: ci sono modi migliori. Rune prende il premio con:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

che sembra ancora migliore in C normale (grazie a Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}

Non sono sicuro di capire perché una qualsiasi delle alternative sia migliore, se la bontà include chiarezza o manutenibilità.
dkretz

Vero. Speravo di trovare un modo più ragionevole per farlo, dato che devo farlo abbastanza spesso.
MusiGenesis

3
La maggior parte di questa domanda è una sorta di revisione delle risposte seguenti. Suggeriscilo in modo significativo.
einpoklum

Rimuovi C e C ++ dal titolo e dagli hashtag.
jaskmar

Risposte:


149

Sebbene sia certamente un po 'oscuro, direi che il modo più tipograficamente piacevole per farlo è

for (int i = myArray.Length; i --> 0; )
{
    //do something
}

32
Quando ho letto la tua risposta per la prima volta sembrava che non si sarebbe nemmeno compilata, quindi ho pensato che fossi una persona pazza. Ma è esattamente quello che stavo cercando: un modo migliore di scrivere un ciclo al contrario.
MusiGenesis

3
Penso che i -> 0; è apposta. Questo è ciò che intende per "tipograficamente piacevole"
Johannes Schaub - litb

16
L'ho trovato "tipograficamente confuso" me stesso. Per me, il "-" non sembra corretto a meno che non sia adiacente alla variabile che sta influenzando.
MusiGenesis

26
È troppo oscuro e offuscato. Non scriverei mai qualcosa del genere nel codice di produzione ...
Mihai Todor

9
Aah l' operatore va a (->) fa il trucco!
nawfal

118

In C ++ hai fondamentalmente la scelta tra iterare usando iteratori o indici. A seconda che si disponga di un array semplice o di un array std::vector, si utilizzano tecniche diverse.

Utilizzando std :: vector

Usare gli iteratori

C ++ ti permette di farlo usando std::reverse_iterator:

for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    /* std::cout << *it; ... */
}

Usare gli indici

Il tipo integrale senza segno restituito da nonstd::vector<T>::size è sempre . Può essere maggiore o minore. Questo è fondamentale per il funzionamento del ciclo.std::size_t

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* std::cout << someVector[i]; ... */
}

Funziona, poiché i valori dei tipi integrali senza segno sono definiti mediante modulo il loro conteggio dei bit. Quindi, se stai impostando -N, finisci in(2 ^ BIT_SIZE) -N

Utilizzo di array

Usare gli iteratori

Stiamo usando std::reverse_iteratorper fare l'iterazione.

for(std::reverse_iterator<element_type*> it(a + sizeof a / sizeof *a), itb(a); 
    it != itb; 
    ++it) {
    /* std::cout << *it; .... */
}

Usare gli indici

Possiamo tranquillamente usare std::size_tqui, al contrario di quanto sopra, poiché sizeofritorna sempre std::size_tper definizione.

for(std::size_t i = (sizeof a / sizeof *a) - 1; i != (std::size_t) -1; i--) {
   /* std::cout << a[i]; ... */
}

Evitare le insidie ​​con sizeof applicato ai puntatori

In realtà il modo sopra per determinare la dimensione di un array fa schifo. Se a è effettivamente un puntatore invece di un array (cosa che accade abbastanza spesso ei principianti lo confonderanno), fallirà silenziosamente. Un modo migliore è usare quanto segue, che fallirà in fase di compilazione, se viene fornito un puntatore:

template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];

Funziona ottenendo prima la dimensione dell'array passato e quindi dichiarando di restituire un riferimento a un array di tipo char della stessa dimensione. charè definito per avere sizeofdi: 1. Quindi l'array restituito avrà un sizeofdi: N * 1, che è ciò che stiamo cercando, con solo la valutazione in fase di compilazione e zero overhead di runtime.

Invece di fare

(sizeof a / sizeof *a)

Cambia il tuo codice in modo che ora lo faccia

(sizeof array_size(a))

Inoltre, se il tuo contenitore non ha un iteratore inverso, puoi utilizzare l'implementazione reverse_iterator di boost: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24

il tuo array_size sembra funzionare per gli array allocati staticamente, ma non è riuscito per me quando a era 'new int [7]' per esempio.
Nate Parsons

2
sì, questo è lo scopo :) new int [7] restituisce un puntatore. quindi sizeof (new int [7]) restituisce non 7 * sizeof (int), ma restituisce sizeof (int *). array_size causa un errore di compilazione per quel caso invece di funzionare silenziosamente.
Johannes Schaub - litb

prova anche array_size (+ statically_allocated_array), anch'esso fallito, perché l'operatore + trasforma l'array in un puntatore al suo primo elemento. l'uso di plain sizeof ti darebbe di nuovo le dimensioni di un puntatore
Johannes Schaub - litb

Per prima cosa, perché non utilizzare ende beginin ordine inverso?
Tomáš Zato - Ripristina Monica il

54

In C # , utilizzando Visual Studio 2005 o versioni successive, digita "forr" e premi [TAB] [TAB] . Questo si espanderà in un forciclo che va all'indietro attraverso una raccolta.

È così facile sbagliare (almeno per me), che ho pensato che inserire questo frammento sarebbe stata una buona idea.

Detto questo, mi piace Array.Reverse()/ Enumerable.Reverse()e poi ripeto meglio in avanti - dichiarano più chiaramente l'intento.


41

Vorrei sempre preferirei codice chiara contro ' tipograficamente piacevole ' codice. Quindi, userei sempre:

for (int i = myArray.Length - 1; i >= 0; i--)  
{  
    // Do something ...  
}    

Puoi considerarlo come il modo standard per eseguire il ciclo all'indietro.
Solo i miei due centesimi ...


Lavorato. Non l'ho ancora fatto in C #, ma grazie.
PCPGMR

Allora come se l'array fosse davvero grande (quindi l'indice deve essere di tipo non firmato)? size_t è effettivamente non firmato, non è vero?
lalala

Puoi dichiarare i come uint.Vedi il post di Marc Gravell.
Jack Griffin

Non funzionerà per un tipo non firmato a causa del ritorno a capo, a differenza di quello tipograficamente piacevole. Non bene.
Ocelot

17

In C # utilizzando Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}

Questa è sicuramente la più semplice, ma si noti che nella versione corrente del CLR l'inversione di una lista è sempre un'operazione O (n) piuttosto che l'operazione O (1) che potrebbe essere se è un IList <T>; vedi connect.microsoft.com/VisualStudio/feedback/…
Greg Beech

1
Sembra che .Reverse () crei una copia locale dell'array e quindi lo riporti indietro. Sembra un po 'inefficiente copiare un array solo per poterlo iterare. Immagino che qualsiasi post con "Linq" sia degno di voti positivi. :)
MusiGenesis

C'è un possibile compromesso, ma poi ti imbatti nel problema che la "risposta votata" (i -> 0) potrebbe comportare un codice più lento perché (i) deve essere verificato rispetto alla dimensione dell'array ogni volta che lo è usato come in index.
Keltex

1
Sebbene sia d'accordo sul fatto che Linq sia eccezionale, il problema con questo approccio è che un iteratore non ti consente di selezionare selettivamente gli elementi dell'array: considera la situazione in cui volevi navigare attraverso una parte di un array, ma non l'intero cosa ...
jesses.co.tt

11

Questo è sicuramente il modo migliore per qualsiasi array la cui lunghezza è un tipo integrale con segno. Per gli array le cui lunghezze sono di tipo integrale senza segno (ad esempio un std::vectorin C ++), è necessario modificare leggermente la condizione finale:

for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
    // blah

Se hai appena detto i >= 0, questo è sempre vero per un numero intero senza segno, quindi il ciclo sarà un ciclo infinito.


In C ++ la "i--" andrebbe a capo automaticamente al valore più alto se fossi 0? Mi spiace, sono alterato in C ++.
MusiGenesis

Sorprendente. Ragazzi, mi avete appena aiutato a capire la causa di un bug di riempimento del disco rigido in qualcosa che ho scritto 12 anni fa. Meno male che non lavoro in C ++.
MusiGenesis

la condizione di terminazione dovrebbe essere: i <myArray.size (). Dipende solo dalle proprietà di overflow di unsigned int; non dettagli sull'implementazione di interi e cast. Di conseguenza più facile da leggere e funziona in lingue con rappresentazioni di interi alternati.
ejgottl

Penso che una soluzione migliore per me sia rimanere nel mondo C # dove non posso ferire nessuno.
MusiGenesis

4

Mi sembra buono. Se l'indicizzatore non era firmato (uint ecc.), Potrebbe essere necessario tenerne conto. Chiamami pigro, ma in quel caso (non firmato), potrei semplicemente usare una controvariabile:

uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
    arr[--pos] = 42;
}

(in realtà, anche qui dovresti stare attento a casi come arr.Length = uint.MaxValue ... forse a! = da qualche parte ... ovviamente, questo è un caso molto improbabile!)


La cosa "--pos" è complicata, però. In passato ho avuto colleghi a cui piacevano "correggere" casualmente cose in codice che non sembravano loro giuste.
MusiGenesis

1
Quindi usa .arr.Length-1 e pos--
Marc Gravell

4

In CI piace fare questo:


int i = myArray.Length;
while (i--) {
  myArray[i] = 42;
}

Esempio C # aggiunto da MusiGenesis:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}

Mi piace questo. Mi ci sono voluti uno o due secondi per capire che il tuo metodo funziona. Spero che non ti dispiaccia la versione C # aggiunta (le parentesi graffe extra sono in modo che l'indice abbia lo stesso ambito di un ciclo for).
MusiGenesis

Non sono sicuro che lo userei nel codice di produzione, perché susciterebbe un universale "WTF è questo?" reazione di chi ha dovuto mantenerlo, ma in realtà è più facile da digitare rispetto a un normale ciclo for, e nessun segno meno o> = simboli.
MusiGenesis

1
Peccato che la versione C # abbia bisogno dell'extra "> 0".
MusiGenesis

Non sono sicuro di quale lingua sia. ma il tuo primo frammento di codice NON è certamente C :) solo per dirti l'ovvio. forse volevi scrivere C # e html non è riuscito ad analizzarlo o qualcosa del genere?
Johannes Schaub - litb

@litb: immagino che "myArray.Length" non sia valido C (?), ma la parte del ciclo while funziona e ha un bell'aspetto.
MusiGenesis

3

Il modo migliore per farlo in C ++ è probabilmente usare adattatori iteratori (o meglio, range), che trasformeranno pigramente la sequenza mentre viene attraversata.

Fondamentalmente,

vector<value_type> range;
foreach(value_type v, range | reversed)
    cout << v;

Visualizza l'intervallo "intervallo" (qui è vuoto, ma sono abbastanza sicuro che tu possa aggiungere elementi da solo) in ordine inverso. Ovviamente la semplice iterazione dell'intervallo non è molto utile, ma passare quella nuova gamma ad algoritmi e cose del genere è piuttosto interessante.

Questo meccanismo può essere utilizzato anche per usi molto più potenti:

range | transformed(f) | filtered(p) | reversed

Calcolerà pigramente l'intervallo "intervallo", dove la funzione "f" è applicata a tutti gli elementi, gli elementi per i quali "p" non è vero vengono rimossi e infine l'intervallo risultante viene invertito.

La sintassi pipe è l'IMO più leggibile, dato il suo infisso. L'aggiornamento della libreria Boost.Range in attesa di revisione implementa questo, ma è abbastanza semplice farlo anche da soli. È ancora più interessante con un DSEL lambda per generare la funzione f e il predicato p in linea.


puoi dirci da dove provi "foreach"? È un #define BOOST_FOREACH? Sembra carino fino in fondo.
Johannes Schaub - litb


1

Preferisco un ciclo while. Per me è più chiaro del decremento inella condizione di un ciclo for

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}

0

Userei il codice nella domanda originale, ma se volessi davvero usare foreach e avere un indice intero in C #:

foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
    myArray[i] = 42; 
}

-1

Proverò a rispondere alla mia domanda qui, ma non mi piace nemmeno questo:

for (int i = 0; i < myArray.Length; i++)
{
    int iBackwards = myArray.Length - 1 - i; // ugh
    myArray[iBackwards] = 666;
}

Piuttosto che fare la .Length - 1 - i ogni volta, forse prendere in considerazione una seconda variabile? Vedi il mio post [aggiornato].
Marc Gravell

Ho votato contro la mia stessa domanda. Harsh. Non è più rumoroso dell'originale.
MusiGenesis

-4

NOTA: questo post è finito per essere molto più dettagliato e quindi fuori tema, mi scuso.

Detto questo, i miei colleghi lo leggono e credono che sia prezioso "da qualche parte". Questo thread non è il posto giusto. Apprezzerei il tuo feedback su dove dovrebbe andare (sono nuovo nel sito).


Comunque questa è la versione C # in .NET 3.5 che è sorprendente in quanto funziona su qualsiasi tipo di raccolta utilizzando la semantica definita. Questa è una misura predefinita (riutilizzo!), Non la minimizzazione delle prestazioni o del ciclo della CPU nello scenario di sviluppo più comune, anche se non sembra mai essere ciò che accade nel mondo reale (ottimizzazione prematura).

*** Metodo di estensione che funziona su qualsiasi tipo di raccolta e esegue un'azione delegato che si aspetta un singolo valore del tipo, il tutto eseguito su ogni elemento al contrario **

Requisiti 3.5:

public static void PerformOverReversed<T>(this IEnumerable<T> sequenceToReverse, Action<T> doForEachReversed)
      {
          foreach (var contextItem in sequenceToReverse.Reverse())
              doForEachReversed(contextItem);
      }

Vecchie versioni .NET o vuoi capire meglio gli interni di Linq? Continua a leggere .. O no ..

ASSUNZIONE: Nel sistema di tipi .NET il tipo Array eredita dall'interfaccia IEnumerable (non IEnumerable generico, ma solo IEnumerable).

Questo è tutto ciò di cui hai bisogno per iterare dall'inizio alla fine, tuttavia vuoi muoverti nella direzione opposta. Poiché IEnumerable funziona su Array di tipo 'object', qualsiasi tipo è valido,

MISURA CRITICA: Partiamo dal presupposto che se è possibile elaborare qualsiasi sequenza in ordine inverso che è "migliore", quindi essere in grado di farlo solo su numeri interi.

Soluzione a per .NET CLR 2.0-3.0:

Descrizione: accetteremo qualsiasi istanza di implementazione di IEnumerable con il mandato che ogni istanza in essa contenuta sia dello stesso tipo. Quindi, se riceviamo un array, l'intero array contiene istanze di tipo X. Se altre istanze sono di un tipo! = X viene generata un'eccezione:

Un servizio singleton:

public class ReverserService {private ReverserService () {}

    /// <summary>
    /// Most importantly uses yield command for efficiency
    /// </summary>
    /// <param name="enumerableInstance"></param>
    /// <returns></returns>
    public static IEnumerable ToReveresed(IEnumerable enumerableInstance)
    {
        if (enumerableInstance == null)
        {
            throw new ArgumentNullException("enumerableInstance");
        }

        // First we need to move forwarad and create a temp
        // copy of a type that allows us to move backwards
        // We can use ArrayList for this as the concrete
        // type

        IList reversedEnumerable = new ArrayList();
        IEnumerator tempEnumerator = enumerableInstance.GetEnumerator();

        while (tempEnumerator.MoveNext())
        {
            reversedEnumerable.Add(tempEnumerator.Current);
        }

        // Now we do the standard reverse over this using yield to return
        // the result
        // NOTE: This is an immutable result by design. That is 
        // a design goal for this simple question as well as most other set related 
        // requirements, which is why Linq results are immutable for example
        // In fact this is foundational code to understand Linq

        for (var i = reversedEnumerable.Count - 1; i >= 0; i--)
        {
            yield return reversedEnumerable[i];
        }
    }
}



public static class ExtensionMethods
{

      public static IEnumerable ToReveresed(this IEnumerable enumerableInstance)
      {
          return ReverserService.ToReveresed(enumerableInstance);
      }
 }

[TestFixture] public class Testing123 {

    /// <summary>
    /// .NET 1.1 CLR
    /// </summary>
    [Test]
    public void Tester_fornet_1_dot_1()
    {
        const int initialSize = 1000;

        // Create the baseline data
        int[] myArray = new int[initialSize];

        for (var i = 0; i < initialSize; i++)
        {
            myArray[i] = i + 1;
        }

        IEnumerable _revered = ReverserService.ToReveresed(myArray);

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
    }

    [Test]
    public void tester_why_this_is_good()
    {

        ArrayList names = new ArrayList();
        names.Add("Jim");
        names.Add("Bob");
        names.Add("Eric");
        names.Add("Sam");

        IEnumerable _revered = ReverserService.ToReveresed(names);

        Assert.IsTrue(TestAndGetResult(_revered).Equals("Sam"));


    }

    [Test]
    public void tester_extension_method()
  {

        // Extension Methods No Linq (Linq does this for you as I will show)
        var enumerableOfInt = Enumerable.Range(1, 1000);

        // Use Extension Method - which simply wraps older clr code
        IEnumerable _revered = enumerableOfInt.ToReveresed();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }


    [Test]
    public void tester_linq_3_dot_5_clr()
    {

        // Extension Methods No Linq (Linq does this for you as I will show)
        IEnumerable enumerableOfInt = Enumerable.Range(1, 1000);

        // Reverse is Linq (which is are extension methods off IEnumerable<T>
        // Note you must case IEnumerable (non generic) using OfType or Cast
        IEnumerable _revered = enumerableOfInt.Cast<int>().Reverse();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }



    [Test]
    public void tester_final_and_recommended_colution()
    {

        var enumerableOfInt = Enumerable.Range(1, 1000);
        enumerableOfInt.PerformOverReversed(i => Debug.WriteLine(i));

    }



    private static object TestAndGetResult(IEnumerable enumerableIn)
    {
      //  IEnumerable x = ReverserService.ToReveresed(names);

        Assert.IsTrue(enumerableIn != null);
        IEnumerator _test = enumerableIn.GetEnumerator();

        // Move to first
        Assert.IsTrue(_test.MoveNext());
        return _test.Current;
    }
}

2
Amico o tizio: stavo solo cercando un modo di scrivere meno goffo "for (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis

1
non c'è alcun valore in questa risposta
Matt Melton
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.