Was ist der beste Weg, um eine Rückwärtsschleife in C / C # / C ++ durchzuführen?

101

Ich muss mich rückwärts durch ein Array bewegen, damit ich folgenden Code habe:

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

Gibt es einen besseren Weg, dies zu tun?

Update: Ich hatte gehofft, dass C # vielleicht einen eingebauten Mechanismus dafür hat, wie:

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

Update 2: Es gibt bessere Möglichkeiten. Rune erhält den Preis mit:

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

was in normalem C noch besser aussieht (dank Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}
MusiGenesis
quelle
Ich bin mir nicht sicher, warum eine der Alternativen besser ist, wenn Güte Klarheit oder Wartbarkeit beinhaltet.
Dkretz
Wahr. Ich hatte gehofft, einen vernünftigeren Weg zu finden, da ich es ziemlich oft tun muss.
MusiGenesis
3
Der größte Teil dieser Frage ist eine Art Überprüfung der Antworten unten. Schlagen Sie vor, es deutlich zu verkürzen.
Einpoklum
Bitte entfernen Sie C und C ++ aus dem Titel und den Hashtags.
Jaskmar

Antworten:

148

Obwohl dies zugegebenermaßen etwas dunkel ist, würde ich sagen, dass der typografisch angenehmste Weg, dies zu tun, der ist

for (int i = myArray.Length; i --> 0; )
{
    //do something
}
MusiGenesis
quelle
32
Als ich Ihre Antwort zum ersten Mal las, sah es so aus, als würde sie nicht einmal kompiliert, also nahm ich an, dass Sie eine verrückte Person waren. Aber es ist genau das, wonach ich gesucht habe: eine bessere Möglichkeit, eine Rückwärts-for-Schleife zu schreiben.
MusiGenesis
3
Ich denke, dass ich -> 0; ist absichtlich. Das ist es, was er mit "typografisch ansprechend" meint
Johannes Schaub - litb
16
Ich fand es selbst "typografisch verwirrend". Für mich sieht das "-" nur dann richtig aus, wenn es an die Variable angrenzt, auf die es sich auswirkt.
MusiGenesis
26
Das ist zu dunkel und verschleiert. Ich würde so etwas nie in Produktionscode schreiben ...
Mihai Todor
9
Aah das geht zum Operator (->) macht den Trick!
Nawfal
118

In C ++ haben Sie grundsätzlich die Wahl zwischen Iteration mit Iteratoren oder Indizes. Abhängig davon, ob Sie ein einfaches Array oder einstd::vector , verwenden Sie unterschiedliche Techniken.

Verwenden von std :: vector

Verwenden von Iteratoren

Mit C ++ können Sie dies mit tun std::reverse_iterator:

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

Indizes verwenden

Der vorzeichenlose Integraltyp, der von zurückgegeben wird, std::vector<T>::sizeist nicht immer std::size_t. Es kann größer oder kleiner sein. Dies ist entscheidend, damit die Schleife funktioniert.

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

Es funktioniert, da vorzeichenlose Integraltypwerte mittels Modulo ihrer Bitanzahl definiert werden. Also, wenn Sie einstellen-N , landen Sie bei(2 ^ BIT_SIZE) -N

Arrays verwenden

Verwenden von Iteratoren

Wir verwenden std::reverse_iteratordie Iteration.

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

Indizes verwenden

Wir können hier sicher verwenden std::size_t, im Gegensatz zu oben, da sizeofimmer std::size_tper Definition zurückgegeben wird.

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

Vermeiden von Fallstricken, bei denen sizeof auf Zeiger angewendet wird

Tatsächlich ist die obige Methode zum Bestimmen der Größe eines Arrays zum Kotzen. Wenn a tatsächlich ein Zeiger anstelle eines Arrays ist (was ziemlich häufig vorkommt und Anfänger es verwirren), schlägt es stillschweigend fehl. Eine bessere Möglichkeit besteht darin, Folgendes zu verwenden, das beim Kompilieren fehlschlägt, wenn ein Zeiger angegeben wird:

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

Es funktioniert, indem zuerst die Größe des übergebenen Arrays ermittelt und dann deklariert wird, dass ein Verweis auf ein Array vom Typ char derselben Größe zurückgegeben wird. charist definiert als sizeof: 1. Das zurückgegebene Array hat also asizeof : N * 1, was wir suchen, mit nur Auswertung der Kompilierungszeit und null Laufzeit-Overhead.

Anstatt zu tun

(sizeof a / sizeof *a)

Ändern Sie Ihren Code so, dass er jetzt funktioniert

(sizeof array_size(a))
Johannes Schaub - litb
quelle
Wenn Ihr Container keinen Reverse-Iterator hat, können Sie außerdem die reverse_iterator-Implementierung von boost verwenden: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24
Ihre array_size scheint für statisch zugewiesene Arrays zu funktionieren, ist jedoch für mich fehlgeschlagen, als a beispielsweise 'new int [7]' war.
Nate Parsons
2
Ja, das ist die Absicht davon :) new int [7] gibt einen Zeiger zurück. sizeof (new int [7]) gibt also nicht 7 * sizeof (int) zurück, sondern sizeof (int *). array_size verursacht in diesem Fall einen Kompilierungsfehler, anstatt stillschweigend zu arbeiten.
Johannes Schaub - litb
Versuchen Sie auch array_size (+ statically_allocated_array), was ebenfalls fehlschlägt, da der Operator + das Array zu einem Zeiger auf sein erstes Element macht. Die Verwendung von plain sizeof würde Ihnen wieder die Größe eines Zeigers geben
Johannes Schaub - litb
Für das erste , was - warum nicht einfach verwenden endund beginin umgekehrter Reihenfolge?
Tomáš Zato - Wiedereinsetzung Monica
54

Geben Sie in C # mit Visual Studio 2005 oder höher 'forr' ein und drücken Sie [TAB] [TAB] . Dies wird zu einer forSchleife erweitert, die eine Sammlung rückwärts durchläuft.

Es ist so leicht, sich zu irren (zumindest für mich), dass ich dachte, dieses Snippet einzulegen wäre eine gute Idee.

Das heißt, ich mag Array.Reverse()/ Enumerable.Reverse()und iteriere dann besser vorwärts - sie geben die Absicht klarer an.

Jay Bazuzi
quelle
41

Ich würde immer klaren Code dem typografisch ansprechenden Code vorziehen . Daher würde ich immer verwenden:

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

Sie können dies als Standardmethode für die Rückwärtsschleife betrachten.
Nur meine zwei Cent ...

Jack Griffin
quelle
Hat funktioniert. Habe das noch nicht in C # gemacht, aber danke.
PCPGMR
Wie wäre es also, wenn das Array wirklich groß ist (der Index muss also vom Typ ohne Vorzeichen sein)? size_t ist eigentlich ohne Vorzeichen, nicht wahr?
Lalala
Sie können i als uint deklarieren. Siehe Marc Gravells Beitrag.
Jack Griffin
Funktioniert aufgrund der Umhüllung nicht für einen nicht signierten Typ, im Gegensatz zu dem typografisch ansprechenden. Nicht gut.
Ocelot
17

In C # mit Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}
Keltex
quelle
Dies ist mit Sicherheit die einfachste, aber beachten Sie, dass in der aktuellen Version der CLR das Umkehren einer Liste immer eine O (n) -Operation ist und nicht die O (1) -Operation, die es sein könnte, wenn es sich um eine IList <T> handelt. Siehe connect.microsoft.com/VisualStudio/feedback/…
Greg Beech
1
Sieht so aus, als würde .Reverse () eine lokale Kopie des Arrays erstellen und dann rückwärts durchlaufen. Es scheint ineffizient zu sein, ein Array zu kopieren, nur damit Sie es durchlaufen können. Ich denke, jeder Beitrag mit "Linq" verdient eine Aufwertung. :)
MusiGenesis
Es gibt einen möglichen Kompromiss, aber dann stoßen Sie auf das Problem, dass die "abgestimmte Antwort" (i -> 0) zu einem langsameren Code führen kann, da (i) jedes Mal anhand der Größe des Arrays überprüft werden muss verwendet wie im Index.
Keltex
1
Obwohl ich der Meinung bin, dass Linq großartig ist, besteht das Problem bei diesem Ansatz darin, dass Sie mit einem Iterator nicht selektiv Elemente des Arrays auswählen können. Berücksichtigen Sie die Situation, in der Sie durch einen Teil eines Arrays navigieren möchten, jedoch nicht durch das gesamte Sache ...
jesses.co.tt
11

Dies ist definitiv der beste Weg für jedes Array, dessen Länge ein vorzeichenbehafteter integraler Typ ist. Bei Arrays, deren Länge ein vorzeichenloser Integraltyp ist (z. B. std::vectorin C ++), müssen Sie die Endbedingung geringfügig ändern:

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

Wenn Sie gerade gesagt haben i >= 0, gilt dies immer für eine Ganzzahl ohne Vorzeichen, sodass die Schleife eine Endlosschleife ist.

Adam Rosenfield
quelle
In C ++ würde das "i--" automatisch auf den höchsten Wert umbrechen, wenn ich 0 wäre? Entschuldigung, ich bin C ++ beeinträchtigt.
MusiGenesis
Tolle. Ihr habt mir nur geholfen, die Ursache eines Fehlers beim Ausfüllen der Festplatte in etwas zu verstehen, das ich vor 12 Jahren geschrieben habe. Gut, dass ich nicht in C ++ arbeite.
MusiGenesis
Die Beendigungsbedingung sollte lauten: i <myArray.size (). Hängt nur von den Überlaufeigenschaften von int ohne Vorzeichen ab. keine Implementierungsdetails von Ganzzahlen und Casts. Folglich einfacher zu lesen und arbeitet in Sprachen mit alternativen Ganzzahldarstellungen.
Ejgottl
Ich denke, eine bessere Lösung für mich ist es, in der C # -Welt zu bleiben, in der ich niemanden verletzen kann.
MusiGenesis
4

Sieht gut für mich aus. Wenn der Indexer nicht signiert war (uint usw.), müssen Sie dies möglicherweise berücksichtigen. Nennen Sie mich faul, aber in diesem (nicht signierten) Fall könnte ich nur eine Gegenvariable verwenden:

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

(Eigentlich müssten Sie auch hier auf Fälle wie arr.Length = uint.MaxValue achten ... vielleicht a! = irgendwo ... natürlich ist das ein sehr unwahrscheinlicher Fall!)

Marc Gravell
quelle
Das "--pos" -Ding ist jedoch schwierig. Ich hatte in der Vergangenheit Mitarbeiter, die es liebten, Dinge im Code zufällig zu "korrigieren", die für sie nicht ganz richtig aussahen.
MusiGenesis
1
Verwenden Sie dann .arr.Length-1 und pos--
Marc Gravell
4

In CI machen Sie das gerne:


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

C # -Beispiel von MusiGenesis hinzugefügt:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}
Twotymz
quelle
Ich mag das. Ich habe ein oder zwei Sekunden gebraucht, um festzustellen, dass Ihre Methode funktioniert. Ich hoffe, Ihnen macht die hinzugefügte C # -Version nichts aus (die zusätzlichen geschweiften Klammern sind so, dass der Index den gleichen Umfang hat wie in einer for-Schleife).
MusiGenesis
Ich bin mir nicht sicher, ob ich dies im Produktionscode verwenden würde, da dies ein universelles "WTF ist das?" Reaktion von jedem, der es pflegen musste, aber es ist tatsächlich einfacher zu tippen als eine normale for-Schleife und keine Minuszeichen oder> = Symbole.
MusiGenesis
1
Schade, dass die C # -Version das zusätzliche "> 0" benötigt.
MusiGenesis
Ich bin mir nicht sicher, welche Sprache es ist. aber dein erstes Code-Snippet ist sicherlich NICHT C :) nur um dir das Offensichtliche zu sagen. Vielleicht wollten Sie C # schreiben und HTML konnte es nicht analysieren oder so?
Johannes Schaub - Litb
@litb: Ich denke, die "myArray.Length" ist nicht gültig C (?), aber der while-Loop-Teil funktioniert und sieht gut aus.
MusiGenesis
3

Der beste Weg, dies in C ++ zu tun, ist wahrscheinlich die Verwendung von Iteratoradaptern (oder besser von Bereichsadaptern), die die Sequenz beim Durchlaufen träge transformieren.

Grundsätzlich,

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

Zeigt den Bereich "Bereich" (hier ist er leer, aber ich bin mir ziemlich sicher, dass Sie Elemente selbst hinzufügen können) in umgekehrter Reihenfolge an. Natürlich ist es nicht sehr sinnvoll, den Bereich einfach zu iterieren, aber es ist ziemlich cool, diesen neuen Bereich an Algorithmen und andere Dinge weiterzugeben.

Dieser Mechanismus kann auch für viel leistungsfähigere Zwecke verwendet werden:

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

Berechnet den Bereich "Bereich" träge, wobei die Funktion "f" auf alle Elemente angewendet wird, Elemente, für die "p" nicht wahr ist, entfernt werden und schließlich der resultierende Bereich umgekehrt wird.

Die Pipe-Syntax ist aufgrund ihres Infix die am besten lesbare IMO. Das ausstehende Boost.Range-Bibliotheksupdate implementiert dies, aber es ist ziemlich einfach, es auch selbst zu tun. Mit einem Lambda-DSEL ist es noch cooler, die Funktion f und das Prädikat p inline zu generieren.


quelle
Kannst du uns sagen, woher du "foreach" hast? Ist es ein #define für BOOST_FOREACH? Sieht den ganzen Weg süß aus.
Johannes Schaub - litb
1
// this is how I always do it
for (i = n; --i >= 0;){
   ...
}
Mike Dunlavey
quelle
1

Ich bevorzuge eine while-Schleife. Für mich ist es klarer, als iden Zustand einer for-Schleife zu dekrementieren

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

Ich würde den Code in der ursprünglichen Frage verwenden, aber wenn Sie wirklich foreach verwenden möchten und einen ganzzahligen Index in C # haben möchten:

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

Ich werde versuchen, meine eigene Frage hier zu beantworten, aber das gefällt mir auch nicht wirklich:

for (int i = 0; i < myArray.Length; i++)
{
    int iBackwards = myArray.Length - 1 - i; // ugh
    myArray[iBackwards] = 666;
}
MusiGenesis
quelle
Betrachten Sie vielleicht jedes Mal eine zweite Variable, anstatt die .Length - 1 - i jedes Mal zu machen? Siehe meinen [aktualisierten] Beitrag.
Marc Gravell
Ich habe meine eigene Frage abgelehnt. Hart. Es ist nicht klobiger als das Original.
MusiGenesis
-4

HINWEIS: Dieser Beitrag war viel detaillierter und daher nicht thematisch. Ich entschuldige mich.

Davon abgesehen lesen meine Kollegen es und glauben, dass es "irgendwo" wertvoll ist. Dieser Thread ist nicht der richtige Ort. Ich würde mich über Ihr Feedback freuen, wohin dies führen soll (ich bin neu auf der Website).


Auf jeden Fall ist dies die C # -Version in .NET 3.5, die insofern erstaunlich ist, als sie mit der definierten Semantik für jeden Sammlungstyp funktioniert. Dies ist eine Standardmaßnahme (Wiederverwendung!), Nicht die Leistung oder die Minimierung des CPU-Zyklus im gängigsten Entwicklungsszenario, obwohl dies in der realen Welt niemals der Fall zu sein scheint (vorzeitige Optimierung).

*** Erweiterungsmethode, die über einen beliebigen Sammlungstyp arbeitet und einen Aktionsdelegierten ausführt, der einen einzelnen Wert des Typs erwartet, die alle in umgekehrter Reihenfolge über jedes Element ausgeführt werden **

Anforderungen 3.5:

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

Ältere .NET-Versionen oder möchten Sie Linq-Interna besser verstehen? Lesen Sie weiter .. Oder nicht ..

ANNAHME: Im .NET-Typsystem erbt der Array-Typ von der IEnumerable-Schnittstelle (nicht von der generischen IEnumerable, sondern nur von IEnumerable).

Dies ist alles, was Sie brauchen, um von Anfang bis Ende zu iterieren, aber Sie möchten sich in die entgegengesetzte Richtung bewegen. Da IEnumerable mit einem Array vom Typ 'Objekt' funktioniert, ist jeder Typ gültig.

KRITISCHE MASSNAHME: Wir gehen davon aus, dass Sie eine Sequenz in umgekehrter Reihenfolge verarbeiten können, die "besser" ist, als dies nur für ganze Zahlen möglich ist.

Lösung a für .NET CLR 2.0-3.0:

Beschreibung: Wir akzeptieren jede IEnumerable-Implementierungsinstanz mit dem Mandat, dass jede darin enthaltene Instanz vom gleichen Typ ist. Wenn wir also ein Array erhalten, enthält das gesamte Array Instanzen vom Typ X. Wenn andere Instanzen vom Typ! = X sind, wird eine Ausnahme ausgelöst:

Ein Singleton-Service:

öffentliche Klasse 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] öffentliche Klasse 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;
    }
}
domain.dot.net Team
quelle
2
Typ oder Typ: Ich habe nur nach einer weniger klobigen Schreibweise gesucht "für (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis
1
Es gibt überhaupt keinen Wert in dieser Antwort
Matt Melton