Peut-on faire un pour chaque boucle en java dans l'ordre inverse?


148

J'ai besoin de parcourir une liste dans l'ordre inverse en utilisant Java.

Alors, où cela fait-il avancer:

for(String string: stringList){
//...do something
}

Existe-t-il un moyen d'itérer la liste de chaînes dans l'ordre inverse en utilisant pour chaque syntaxe?

Pour plus de clarté: je sais comment parcourir une liste dans l'ordre inverse mais j'aimerais savoir (par curiosité) comment le faire dans le pour chaque style.


5
Le but de la boucle "for-each" est que vous avez juste besoin d'effectuer une opération sur chaque élément, et l'ordre n'est pas important. For-each pourrait traiter les éléments dans un ordre complètement aléatoire, et il ferait toujours ce pour quoi il a été conçu. Si vous avez besoin de traiter les éléments d'une manière particulière, je vous suggère de le faire manuellement.
muusbolla

Bibliothèque de collections Java. Pas vraiment rien à voir avec la langue. Blâmez Josh Bloch.
Tom Hawtin - tackline

7
@muusbolla: Mais comme une liste est une collection commandée , sa commande sera sûrement honorée, peu importe? Par conséquent, for-each ne traitera pas les éléments d'une liste dans un ordre aléatoire.
Lee Kowalkowski

5
@muusbolla ce n'est pas vrai. Peut-être dans le cas des Setcollections dérivées. foreachgarantit l'itération dans l'ordre de l'itérateur renvoyé par la iterator()méthode de la collection. docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html
robert

Réponses:


151

La méthode Collections.reverse retourne en fait une nouvelle liste avec les éléments de la liste d'origine copiés dans l'ordre inverse, ce qui a des performances O (n) en ce qui concerne la taille de la liste d'origine.

Comme solution plus efficace, vous pouvez écrire un décorateur qui présente une vue inversée d'une liste comme un itérable. L'itérateur retourné par votre décorateur utilise le ListIterator de la liste décorée pour parcourir les éléments dans l'ordre inverse.

Par exemple:

public class Reversed<T> implements Iterable<T> {
    private final List<T> original;

    public Reversed(List<T> original) {
        this.original = original;
    }

    public Iterator<T> iterator() {
        final ListIterator<T> i = original.listIterator(original.size());

        return new Iterator<T>() {
            public boolean hasNext() { return i.hasPrevious(); }
            public T next() { return i.previous(); }
            public void remove() { i.remove(); }
        };
    }

    public static <T> Reversed<T> reversed(List<T> original) {
        return new Reversed<T>(original);
    }
}

Et vous l'utiliseriez comme:

import static Reversed.reversed;

...

List<String> someStrings = getSomeStrings();
for (String s : reversed(someStrings)) {
    doSomethingWith(s);
}

22
C'est essentiellement ce que fait Iterables.reverse de Google, oui :)
Jon Skeet

10
Je sais qu'il y a une "règle" selon laquelle nous devons accepter la réponse de Jon :) mais .. je veux accepter celle-ci (même si elles sont essentiellement les mêmes) car elle ne m'oblige pas à inclure une autre bibliothèque tierce (même si certains pourraient soutenir que cette raison brise l'un des principaux avantages de l'OO - la réutilisabilité).
Ron Tuffin

Petite erreur: dans public void remove (), il ne devrait pas y avoir d'instruction return, elle devrait être juste: i.remove ();
Jesper

10
Collections.reverse () ne renvoie PAS une copie inversée mais agit sur la liste qui lui est passée en paramètre. Comme votre solution avec l'itérateur, cependant. Vraiment élégant.
er4z0r

96

Pour une liste, vous pouvez utiliser la bibliothèque Google Guava :

for (String item : Lists.reverse(stringList))
{
    // ...
}

Notez que cela n'inverse pas toute la collection, ou ne fait rien de semblable - cela permet simplement l'itération et l'accès aléatoire, dans l'ordre inverse. C'est plus efficace que d'annuler d'abord la collection.Lists.reverse

Pour inverser un itérable arbitraire, vous devez tout lire et le «rejouer» à l'envers.

(Si vous n'êtes pas déjà l' utiliser, je bien vous recommandons de jeter un oeil à la goyave . Il est super choses.)


1
Notre base de code fait un usage intensif de la version générée de Commons Collections publiée par larvalabs ( larvalabs.com/collections ). En parcourant le dépôt SVN pour Apache Commons, il est clair que la plupart du travail de publication d'une version java 5 de Commons Collections est terminé, ils ne l'ont tout simplement pas encore publié.
skaffman

Je l'aime. Si ce n'était pas si utile, j'appellerais ça une prise.
geowa4

Je me demande pourquoi Jakarta n'a jamais pris la peine de mettre à jour Apache Commons.
Uri

3
Ils l'ont mis à jour, c'est ce que je dis. Ils ne l'ont tout simplement pas publié.
skaffman

23
Iterables.reverse est obsolète, utilisez plutôt Lists.reverse ou ImmutableList.reverse.
Garrett Hall

39

La Liste (contrairement à l'Ensemble) est une collection ordonnée et l'itération sur elle préserve la commande par contrat. Je me serais attendu à ce qu'un Stack itère dans l'ordre inverse, mais malheureusement ce n'est pas le cas. La solution la plus simple à laquelle je puisse penser est donc la suivante:

for (int i = stack.size() - 1; i >= 0; i--) {
    System.out.println(stack.get(i));
}

Je me rends compte que ce n'est pas une solution de boucle «pour chaque». Je préfère utiliser la boucle for plutôt que d'introduire une nouvelle bibliothèque comme les collections Google.

Collections.reverse () fait également le travail, mais il met à jour la liste au lieu de renvoyer une copie dans l'ordre inverse.


3
Cette approche peut convenir pour les listes basées sur des tableaux (comme ArrayList), mais elle serait sous-optimale pour les listes liées car chaque obtention devrait parcourir la liste du début à la fin (ou éventuellement de la fin au début) pour chaque obtention. Il est préférable d'utiliser un itérateur plus intelligent comme dans la solution de Nat (optimal pour toutes les implémentations de List).
Chris

1
De plus, il s'écarte de la requête dans l'OP qui demande explicitement la for eachsyntaxe
Paul W

8

Cela va perturber la liste d'origine et doit également être appelé en dehors de la boucle. De plus, vous ne voulez pas effectuer une inversion à chaque fois que vous bouclez - serait-ce vrai si l'un des Iterables.reverse ideasétait appliqué?

Collections.reverse(stringList);

for(String string: stringList){
//...do something
}

5

AFAIK il n'y a pas une sorte de chose standard "reverse_iterator" dans la bibliothèque standard qui supporte la syntaxe for-each qui est déjà un sucre syntaxique qu'ils ont introduit tardivement dans le langage.

Vous pouvez faire quelque chose comme pour (élément Item: myList.clone (). Reverse ()) et payer le prix associé.

Cela semble également assez cohérent avec le phénomène apparent de ne pas vous donner de moyens pratiques pour effectuer des opérations coûteuses - car une liste, par définition, pourrait avoir une complexité d'accès aléatoire O (N) (vous pouvez implémenter l'interface avec un lien unique), inverser l'itération pourrait finir par être O (N ^ 2). Bien sûr, si vous avez une ArrayList, vous ne payez pas ce prix.


Vous pouvez exécuter un ListIterator à l'envers, qui peut être encapsulé dans un Iterator.
Tom Hawtin - tackline

@Tom: Bon point. Cependant, avec l'itérateur, vous faites toujours le vieux style ennuyeux des boucles for, et vous pouvez toujours payer le coût pour arriver au dernier élément pour commencer ... J'ai ajouté le qualificatif dans ma réponse, cependant, merci.
Uri

Deque a un itérateur inverse.
Michael Munsey

2

Cela peut être une option. J'espère qu'il existe une meilleure façon de commencer à partir du dernier élément que de faire une boucle while jusqu'à la fin.

public static void main(String[] args) {        
    List<String> a = new ArrayList<String>();
    a.add("1");a.add("2");a.add("3");a.add("4");a.add("5");

    ListIterator<String> aIter=a.listIterator();        
    while(aIter.hasNext()) aIter.next();

    for (;aIter.hasPrevious();)
    {
        String aVal = aIter.previous();
        System.out.println(aVal);           
    }
}

2

Au moment du commentaire : vous devriez pouvoir utiliser Apache CommonsReverseListIterator

Iterable<String> reverse 
    = new IteratorIterable(new ReverseListIterator(stringList));

for(String string: reverse ){
    //...do something
}

Comme @rogerdpack dit , vous avez besoin d'envelopper le ReverseListIteratorcomme Iterable.


1

Non sans écrire du code personnalisé qui vous donnera un énumérateur qui inversera les éléments pour vous.

Vous devriez pouvoir le faire en Java en créant une implémentation personnalisée de Iterable qui renverra les éléments dans l'ordre inverse.

Ensuite, vous instanciez le wrapper (ou appelez la méthode, what-have-you) qui renverrait l'implémentation Iterable qui inverse l'élément dans la boucle for each.



1

Vous devrez inverser votre collection si vous souhaitez utiliser le pour chaque syntaxe prête à l'emploi et aller dans l'ordre inverse.


1

Toutes les réponses ci-dessus ne remplissent que l'exigence, soit en enveloppant une autre méthode ou en appelant un code étranger à l'extérieur;

Voici la solution copiée de la 4ème édition de Thinking in Java , chapitre 11.13.1 AdapterMethodIdiom ;

Voici le code:

// The "Adapter Method" idiom allows you to use foreach
// with additional kinds of Iterables.
package holding;
import java.util.*;

@SuppressWarnings("serial")
class ReversibleArrayList<T> extends ArrayList<T> {
  public ReversibleArrayList(Collection<T> c) { super(c); }
  public Iterable<T> reversed() {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return new Iterator<T>() {
          int current = size() - 1; //why this.size() or super.size() wrong?
          public boolean hasNext() { return current > -1; }
          public T next() { return get(current--); }
          public void remove() { // Not implemented
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }
}   

public class AdapterMethodIdiom {
  public static void main(String[] args) {
    ReversibleArrayList<String> ral =
      new ReversibleArrayList<String>(
        Arrays.asList("To be or not to be".split(" ")));
    // Grabs the ordinary iterator via iterator():
    for(String s : ral)
      System.out.print(s + " ");
    System.out.println();
    // Hand it the Iterable of your choice
    for(String s : ral.reversed())
      System.out.print(s + " ");
  }
} /* Output:
To be or not to be
be to not or be To
*///:~

pourquoi le int current = size() - 1droit? pourquoi pas le int current = this.size() - 1orint current = super.size() - 1
qiwen li

1

Un travail autour:

Collections.reverse(stringList).forEach(str -> ...);

Ou avec de la goyave :

Lists.reverse(stringList).forEach(str -> ...);

0

Certainement une réponse tardive à cette question. Une possibilité est d'utiliser le ListIterator dans une boucle for. Ce n'est pas aussi clair que la syntaxe deux-points, mais cela fonctionne.

List<String> exampleList = new ArrayList<>();
exampleList.add("One");
exampleList.add("Two");
exampleList.add("Three");

//Forward iteration
for (String currentString : exampleList) {
    System.out.println(currentString); 
}

//Reverse iteration
for (ListIterator<String> itr = exampleList.listIterator(exampleList.size()); itr.hasPrevious(); /*no-op*/ ) {
    String currentString = itr.previous();
    System.out.println(currentString); 
}

Le mérite de la syntaxe ListIterator va aux "façons d'itérer sur une liste en Java"

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.