Quelle est la meilleure façon de faire une boucle arrière en C / C # / C ++?


101

J'ai besoin de reculer dans un tableau, j'ai donc un code comme celui-ci:

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

Existe-t-il une meilleure façon de le faire?

Mise à jour: j'espérais que C # avait peut-être un mécanisme intégré pour cela, comme:

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

Mise à jour 2: il existe de meilleures façons. Rune remporte le prix avec:

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

ce qui est encore mieux en C normal (grâce à Twotymz):

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

Je ne suis pas sûr de voir pourquoi l'une des alternatives est meilleure, si la bonté inclut la clarté ou la maintenabilité.
dkretz

Vrai. J'espérais trouver un moyen plus raisonnable de le faire, car je dois le faire assez souvent.
MusiGenesis

3
La plupart de cette question est en quelque sorte une revue des réponses ci-dessous. Suggérez-le d'être raccourci de manière significative.
einpoklum

Veuillez supprimer C et C ++ du titre et des hashtags.
jaskmar le

Réponses:


149

Bien que certes un peu obscur, je dirais que la manière la plus typographiquement la plus agréable de le faire est

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

32
Quand j'ai lu votre réponse pour la première fois, il semblait qu'elle ne se compilerait même pas, alors j'ai supposé que vous étiez une personne folle. Mais c'est exactement ce que je recherchais: une meilleure façon d'écrire une boucle for inversée.
MusiGenesis

3
Je pense que i -> 0; est exprès. C'est ce qu'il entend par «typographiquement agréable»
Johannes Schaub - litb

16
Je l'ai trouvé «typographiquement déroutant» moi-même. Pour moi, le "-" n'a pas l'air correct sauf s'il est adjacent à la variable qu'il affecte.
MusiGenesis

26
C'est trop obscur et obscurci. Je n'écrirais jamais quelque chose comme ça dans le code de production ...
Mihai Todor

9
Aah l' opérateur va à (->) fait l'affaire!
nawfal

118

En C ++, vous avez fondamentalement le choix entre des itérations à l'aide d'itérateurs ou d'index. Selon que vous disposez d'un tableau simple ou a std::vector, vous utilisez différentes techniques.

Utilisation de std :: vector

Utiliser des itérateurs

C ++ vous permet de faire cela en utilisant std::reverse_iterator:

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

Utilisation d'indices

Le type intégral non signé renvoyé par std::vector<T>::sizen'est pas toujours std::size_t. Cela peut être supérieur ou inférieur. Ceci est crucial pour que la boucle fonctionne.

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

Cela fonctionne, car les valeurs des types intégraux non signés sont définies au moyen de modulo leur nombre de bits. Ainsi, si vous réglez -N, vous vous retrouvez à(2 ^ BIT_SIZE) -N

Utilisation de tableaux

Utiliser des itérateurs

Nous utilisons std::reverse_iteratorpour faire l'itération.

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

Utilisation d'indices

Nous pouvons utiliser en toute sécurité std::size_tici, par opposition à ci-dessus, car sizeofrenvoie toujours std::size_tpar définition.

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

Éviter les pièges avec sizeof appliqué aux pointeurs

En fait, la manière ci-dessus de déterminer la taille d'un tableau est nul. Si a est en fait un pointeur au lieu d'un tableau (ce qui arrive assez souvent, et les débutants le confondront), il échouera silencieusement. Une meilleure façon est d'utiliser ce qui suit, qui échouera au moment de la compilation, si un pointeur est donné:

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

Cela fonctionne en obtenant d'abord la taille du tableau passé, puis en déclarant de renvoyer une référence à un tableau de type char de la même taille. charest défini comme ayant sizeofde: 1. Ainsi, le tableau retourné aura un sizeofde: N * 1, ce que nous recherchons, avec seulement une évaluation du temps de compilation et une surcharge d'exécution nulle.

Au lieu de faire

(sizeof a / sizeof *a)

Changez votre code pour qu'il le fasse maintenant

(sizeof array_size(a))

De plus, si votre conteneur n'a pas d'itérateur inversé, vous pouvez utiliser l'implémentation reverse_iterator de boost: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24

votre array_size semble fonctionner pour les tableaux alloués statiquement, mais a échoué pour moi quand a était 'new int [7]' par exemple.
Nate Parsons

2
ouais c'est l'intention :) new int [7] renvoie un pointeur. donc sizeof (new int [7]) ne renvoie pas 7 * sizeof (int), mais retournerait sizeof (int *). array_size provoque une erreur de compilation pour ce cas au lieu de travailler en silence.
Johannes Schaub - litb

essayez également array_size (+ statically_allocated_array), qui échoue également, car l'opérateur + transforme le tableau en un pointeur vers son premier élément. l'utilisation de la taille ordinaire vous donnerait à nouveau la taille d'un pointeur
Johannes Schaub - litb

Pour la première chose - pourquoi ne pas simplement utiliser endet begindans l'ordre inverse?
Tomáš Zato - Réintégrer Monica le

54

En C # , à l'aide de Visual Studio 2005 ou version ultérieure, tapez «forr» et appuyez sur [TAB] [TAB] . Cela s'étendra à une forboucle qui va en arrière dans une collection.

Il est si facile de se tromper (du moins pour moi), que j'ai pensé que mettre cet extrait de code serait une bonne idée.

Cela dit, j'aime Array.Reverse()/ Enumerable.Reverse()puis je répète avant de mieux - ils plus clairement l' intention déclarent.


41

Je préférerais toujours un code clair au code « typographiquement agréable ». Ainsi, j'utiliserais toujours:

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

Vous pouvez le considérer comme le moyen standard de boucler en arrière.
Juste mes deux cents...


Travaillé. Je n'ai pas encore fait cela en C #, mais merci.
PCPGMR

Alors, que faire si le tableau est vraiment grand (donc l'index doit être de type non signé)? size_t est en fait non signé, n'est-ce pas?
lalala

Vous pouvez déclarer i comme uint.Voir l'article de Marc Gravell.
Jack Griffin

Ne fonctionnera pas pour un type non signé en raison de l'enroulement, contrairement à celui qui est typographiquement agréable. Pas bon.
Ocelot le

17

En C # avec Linq :

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

C'est certainement le plus simple, mais notez que dans la version actuelle du CLR, l'inversion d'une liste est toujours une opération O (n) plutôt que l'opération O (1), cela pourrait être si c'est un IList <T>; voir connect.microsoft.com/VisualStudio/feedback/…
Greg Beech

1
On dirait que .Reverse () fait une copie locale du tableau, puis l'itère en arrière. Il semble un peu inefficace de copier un tableau juste pour pouvoir le parcourir. Je suppose que tout message avec "Linq" est digne de votes positifs. :)
MusiGenesis

Il y a un compromis possible, mais alors vous rencontrez le problème que le "vote sur réponse" (i -> 0) pourrait entraîner un code plus lent car (i) doit être vérifié par rapport à la taille du tableau à chaque fois qu'il est utilisé comme dans l'index.
Keltex

1
Bien que je convienne que Linq est excellent, le problème avec cette approche est qu'un itérateur ne vous permet pas de sélectionner sélectivement les éléments du tableau - considérez la situation dans laquelle vous vouliez naviguer à travers une partie d'un tableau, mais pas la totalité thing ...
jesses.co.tt

11

C'est certainement le meilleur moyen pour tout tableau dont la longueur est un type intégral signé. Pour les tableaux dont les longueurs sont de type intégral non signé (par exemple un std::vectoren C ++), vous devez modifier légèrement la condition de fin:

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

Si vous venez de dire i >= 0, c'est toujours vrai pour un entier non signé, donc la boucle sera une boucle infinie.


En C ++, le "i--" serait automatiquement renvoyé à la valeur la plus élevée si i était 0? Désolé, je suis handicapé en C ++.
MusiGenesis

Incroyable. Vous venez de m'aider à comprendre la cause d'un bug de remplissage du disque dur dans quelque chose que j'ai écrit il y a 12 ans. Heureusement que je ne travaille pas en C ++.
MusiGenesis

la condition de terminaison doit être: i <myArray.size (). Dépend uniquement des propriétés de débordement de unsigned int; pas les détails d'implémentation des entiers et des casts. Par conséquent, plus facile à lire et fonctionne dans des langues avec des représentations entières alternatives.
ejgottl

Je pense qu'une meilleure solution pour moi est de rester dans le monde C # où je ne peux blesser personne.
MusiGenesis

4

Cela me semble correct. Si l'indexeur n'était pas signé (uint, etc.), vous devrez peut-être en tenir compte. Appelez-moi paresseux, mais dans ce cas (non signé), je pourrais simplement utiliser une contre-variable:

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

(en fait, même ici, vous devez faire attention aux cas comme arr.Length = uint.MaxValue ... peut-être a! = quelque part ... bien sûr, c'est un cas très improbable!)


La chose "--pos" est cependant délicate. Dans le passé, j'ai eu des collègues qui aimaient «corriger» au hasard des choses dans du code qui ne leur semblaient pas tout à fait correctes.
MusiGenesis

1
Puis utilisez .arr.Length-1 et pos--
Marc Gravell

4

En CI, faites ceci:


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

Exemple C # ajouté par MusiGenesis:

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

J'aime ça. Il m'a fallu une seconde ou deux pour réaliser que votre méthode fonctionne. J'espère que cela ne vous dérange pas la version C # ajoutée (les accolades supplémentaires sont telles que l'index a la même portée que dans une boucle for).
MusiGenesis

Je ne suis pas sûr que j'utiliserais cela dans le code de production, car cela provoquerait un "WTF est-ce?" réaction de celui qui devait le maintenir, mais il est en fait plus facile à taper qu'une boucle for normale, et pas de signe moins ou> = symboles.
MusiGenesis

1
Dommage que la version C # ait besoin du supplément "> 0".
MusiGenesis

Je ne sais pas de quelle langue il s'agit. mais votre premier extrait de code n'est certainement PAS C :) juste pour vous dire l'évidence. peut-être que vous vouliez écrire C # et html n'a pas réussi à l'analyser ou quelque chose?
Johannes Schaub - litb

@litb: Je suppose que "myArray.Length" n'est pas un C valide (?), mais la partie boucle while fonctionne et a l'air super.
MusiGenesis

3

La meilleure façon de faire cela en C ++ est probablement d'utiliser des adaptateurs d'itérateur (ou mieux, de plage), qui transformeront paresseusement la séquence lorsqu'elle est parcourue.

Fondamentalement,

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

Affiche la plage "range" (ici, elle est vide, mais je suis assez sûr que vous pouvez ajouter des éléments vous-même) dans l'ordre inverse. Bien sûr, il n'est pas très utile d'itérer simplement la plage, mais passer cette nouvelle plage à des algorithmes et à d'autres choses est plutôt cool.

Ce mécanisme peut également être utilisé pour des utilisations beaucoup plus puissantes:

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

Calculera paresseusement la plage "plage", où la fonction "f" est appliquée à tous les éléments, les éléments pour lesquels "p" n'est pas vrai sont supprimés, et finalement la plage résultante est inversée.

La syntaxe de pipe est la plus lisible de l'OMI, étant donné son infixe. La mise à jour de la bibliothèque Boost.Range en attente de révision l'implémente, mais il est assez simple de le faire vous-même également. C'est encore plus cool avec un DSEL lambda de générer la fonction f et le prédicat p en ligne.


pouvez-vous nous dire d'où vous avez "foreach"? Est-ce une #define pour BOOST_FOREACH? Ça a l'air mignon tout du long.
Johannes Schaub - litb


1

Je préfère une boucle while. C'est plus clair pour moi que de décrémenter idans la condition d'une boucle for

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

0

J'utiliserais le code dans la question d'origine, mais si vous vouliez vraiment utiliser foreach et avoir un index entier en C #:

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

-1

Je vais essayer de répondre à ma propre question ici, mais je n'aime pas vraiment ça non plus:

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

Plutôt que de faire le .Length - 1 - i à chaque fois, envisagez peut-être une deuxième variable? Voir mon message [mis à jour].
Marc Gravell

J'ai voté à la baisse sur ma propre question. Dur. Ce n'est pas plus maladroit que l'original.
MusiGenesis

-4

REMARQUE: Ce message a fini par être beaucoup plus détaillé et donc hors sujet, je m'excuse.

Cela étant dit, mes pairs le lisent et pensent qu'il est utile «quelque part». Ce fil n'est pas l'endroit. J'apprécierais vos commentaires sur où cela devrait aller (je suis nouveau sur le site).


Quoi qu'il en soit, c'est la version C # dans .NET 3.5 qui est incroyable en ce qu'elle fonctionne sur n'importe quel type de collection en utilisant la sémantique définie. Il s'agit d'une mesure par défaut (réutilisation!) Et non de la minimisation des performances ou du cycle du processeur dans la plupart des scénarios de développement courants, bien que cela ne semble jamais être ce qui se passe dans le monde réel (optimisation prématurée).

*** Méthode d'extension travaillant sur n'importe quel type de collection et prenant un délégué d'action attend une valeur unique du type, le tout exécuté sur chaque élément à l'envers **

Requres 3.5:

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

Anciennes versions de .NET ou souhaitez-vous mieux comprendre les composants internes de Linq? Continuez à lire… Ou pas…

HYPOTHÈSE: Dans le système de type .NET, le type Array hérite de l'interface IEnumerable (et non du IEnumerable générique uniquement IEnumerable).

C'est tout ce dont vous avez besoin pour parcourir du début à la fin, mais vous souhaitez vous déplacer dans la direction opposée. Comme IEnumerable fonctionne sur un tableau de type 'objet', tout type est valide,

MESURE CRITIQUE: Nous supposons que si vous pouvez traiter n'importe quelle séquence dans l'ordre inverse qui est «meilleure», alors ne pouvoir le faire que sur des entiers.

Solution a pour .NET CLR 2.0-3.0:

Description: Nous accepterons toute instance d'implémentation IEnumerable avec le mandat que chaque instance qu'elle contient est du même type. Donc, si nous recevons un tableau, le tableau entier contient des instances de type X. Si d'autres instances sont d'un type! = X, une exception est levée:

Un service singleton:

classe publique 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] classe publique 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
Mec ou dudette: je cherchais juste une manière moins maladroite d'écrire "for (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis

1
il n'y a aucune valeur dans cette réponse du tout
Matt Melton
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.