Parcourir une liste dans l'ordre inverse en Java

251

Je migre un morceau de code pour utiliser des génériques. Un argument pour cela est que la boucle for est beaucoup plus propre que de garder une trace des index, ou d'utiliser un itérateur explicite.

Dans environ la moitié des cas, la liste (une ArrayList) est itérée dans l'ordre inverse en utilisant un index aujourd'hui.

Quelqu'un peut-il suggérer une façon plus propre de le faire (car je n'aime pas cela indexed for looplorsque je travaille avec des collections), bien que cela fonctionne?

 for (int i = nodes.size() - 1; i >= 0; i--) {
    final Node each = (Node) nodes.get(i);
    ...
 }

Remarque: je ne peux pas ajouter de nouvelles dépendances en dehors du JDK.

Allain Lalonde
la source
10
Qu'y a-t-il de si mal à utiliser un index explicite pour itérer sur une structure de données indexée? Au moins, cela vous montre ce qui se passe exactement. Pour itérer en arrière, j'ai toujours l'idiome légèrement plus court suivant:for (int i = nodes.size(); --i >= 0;)
x4u
4
Rien de particulier, je préfère simplement programmer sur une interface et ne pas savoir quel type de liste j'utilise. Bien que j'aime beaucoup votre petite main. (+1 commentaire)
Allain Lalonde
1
@ x4u: il n'y a pas grand-chose, bien qu'Iterator soit rapide et permet également de supprimer facilement les éléments pendant l'itération.
Adamski
2
Cette classe est rompue, car l'utilisateur peut vouloir répéter une seconde fois sur le même Iterable, ou la liste peut changer entre le moment où l'itérable est construit et celui où il est itéré. Je suis sûr que pour vos besoins, vous vous assurerez de ne pas le faire, mais il ne serait pas trop difficile de corriger le code; ou volez simplement le code à Guava (licence Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion
1
Assez juste, mais même celui de la goyave est susceptible de la même chose si je le lis correctement. Si un utilisateur conservait une copie du résultat à l'envers, il aurait le même problème.
Allain Lalonde du

Réponses:

447

Essaye ça:

// Substitute appropriate type.
ArrayList<...> a = new ArrayList<...>();

// Add elements to list.

// Generate an iterator. Start just after the last element.
ListIterator li = a.listIterator(a.size());

// Iterate in reverse.
while(li.hasPrevious()) {
  System.out.println(li.previous());
}
John Feminella
la source
4
Pas mal. N'utilise pas l'index, mais perd l'élégance de la pour chaque syntaxe. +1 de toute façon.
Allain Lalonde
26
Appeler listIterator () sans argument d'index donnera un Iterator au début de la liste et donc hasPrevious () retournera false au premier appel.
Adamski
2
Vous voulez un index sur l' listIteratorappel, je pense.
Tom Hawtin - tackline
1
Vous pouvez écrire un Iteratorqui utilise un ListIteratorinversé, mais cela ne vaut peut-être pas la peine pour une boucle.
Tom Hawtin - tackline
1
Ce n'est pas une boucle, j'ai donc pris cela et l'ai enveloppé. pastebin.ca/1759041 donc maintenant je peux le fairefor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde
35

Offres de goyaveLists#reverse(List) et ImmutableList#reverse(). Comme dans la plupart des cas pour Guava, les anciens délèguent à ces derniers si l'argument est un ImmutableList, vous pouvez donc utiliser les premiers dans tous les cas. Ceux-ci ne créent pas de nouvelles copies de la liste mais simplement des "vues inversées" de celle-ci.

Exemple

List reversed = ImmutableList.copyOf(myList).reverse();
Geoffrey Zheng
la source
23

Je ne pense pas qu'il soit possible d'utiliser la syntaxe de boucle for. La seule chose que je peux suggérer est de faire quelque chose comme:

Collections.reverse(list);
for (Object o : list) {
  ...
}

... mais je ne dirais pas que c'est "plus propre" étant donné que ça va être moins efficace.

Adamski
la source
26
et cela modifie également la liste que vous parcourez, ce qui est un effet secondaire énorme. (Supposons que vous enveloppiez cela dans une méthode, chaque fois qu'il est appelé, vous parcourez la liste dans l'autre sens ^^)
jolivier
Ceci est la solution la plus propre.
jfajunior
14

Option 1: Avez-vous pensé à inverser la liste avec Collections # reverse () puis à utiliser foreach?

Bien sûr, vous voudrez peut-être également refactoriser votre code afin que la liste soit ordonnée correctement afin que vous n'ayez pas à l'inverser, ce qui utilise un espace / temps supplémentaire.


ÉDITER:

Option 2: Alternativement, pourriez-vous utiliser un Deque au lieu d'un ArrayList? Il vous permettra d'itérer en avant et en arrière


ÉDITER:

Option 3: Comme d'autres l'ont suggéré, vous pouvez écrire un Iterator qui parcourra la liste à l'envers, voici un exemple:

import java.util.Iterator;
import java.util.List;

public class ReverseIterator<T> implements Iterator<T>, Iterable<T> {

    private final List<T> list;
    private int position;

    public ReverseIterator(List<T> list) {
        this.list = list;
        this.position = list.size() - 1;
    }

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    public boolean hasNext() {
        return position >= 0;
    }

    @Override
    public T next() {
        return list.get(position--);
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

}


List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");

for (String s : new ReverseIterator<String>(list)) {
    System.out.println(s);
}
Kevin
la source
1
J'y ai pensé, mais il y a un coût prohibitif à inverser. En outre, il ne nécessite ce type d'itération que la moitié du temps. Le retourner ne ferait que déplacer le problème vers l'autre moitié.
Allain Lalonde
3
+ pour Deque; implémentations typiques ont descendingIterator().
trashgod
Ce serait terrible pour les listes chaînées, la variante de l'OP est meilleure.
masterxilo
1
L'utilisation d'une for eachexpression est à mon avis la solution la plus idiomatique. Il est agréable de réaliser que cela est possible si votre liste implémente Iterable d'une manière qui se répète en arrière. Je vais utiliser cette approche et utiliser la classe ReverseListIterator des collections Apache Commons.
David Groomes
11

Vous pouvez utiliser la classe concrète au LinkedListlieu de l'interface générale List. Ensuite, vous avez un descendingIteratorpour itérer avec la direction inverse.

LinkedList<String > linkedList;
for( Iterator<String > it = linkedList.descendingIterator(); it.hasNext(); ) {
    String text = it.next();
}

Je ne sais pas pourquoi il n'y a pas descendingIteratorde ArrayList...

tangens
la source
9

C'est une vieille question, mais il manque une réponse adaptée à java8. Voici quelques façons de réitérer la liste, à l'aide de l'API Streaming:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 3, 3, 7, 5));
list.stream().forEach(System.out::println); // 1 3 3 7 5

int size = list.size();

ListIterator<Integer> it = list.listIterator(size);
Stream.generate(it::previous).limit(size)
    .forEach(System.out::println); // 5 7 3 3 1

ListIterator<Integer> it2 = list.listIterator(size);
Stream.iterate(it2.previous(), i -> it2.previous()).limit(size)
    .forEach(System.out::println); // 5 7 3 3 1

// If list is RandomAccess (i.e. an ArrayList)
IntStream.range(0, size).map(i -> size - i - 1).map(list::get)
    .forEach(System.out::println); // 5 7 3 3 1

// If list is RandomAccess (i.e. an ArrayList), less efficient due to sorting
IntStream.range(0, size).boxed().sorted(Comparator.reverseOrder())
    .map(list::get).forEach(System.out::println); // 5 7 3 3 1
Federico Peralta Schaffner
la source
2
très agréable, juste un changement cosmétique: int size = list.size (); ListIterator <Integer> it = list.listIterator (taille); Stream.generate (it :: previous) .limit (size) .forEach (System.out :: println);
benez
5

Voici une implémentation (non testée) de a ReverseIterable. Quand iterator()est appelé, il crée et renvoie une ReverseIteratorimplémentation privée , qui mappe simplement les appels hasNext()vers hasPrevious()et les appels vers next()sont mappés vers previous(). Cela signifie que vous pouvez parcourir une ArrayListmarche arrière comme suit:

ArrayList<String> l = ...
for (String s : new ReverseIterable(l)) {
  System.err.println(s);
}

Définition de classe

public class ReverseIterable<T> implements Iterable<T> {
  private static class ReverseIterator<T> implements Iterator {
    private final ListIterator<T> it;

    public boolean hasNext() {
      return it.hasPrevious();
    }

    public T next() {
      return it.previous();
    }

    public void remove() {
      it.remove();
    }
  }

  private final ArrayList<T> l;

  public ReverseIterable(ArrayList<T> l) {
    this.l = l;
  }

  public Iterator<T> iterator() {
    return new ReverseIterator(l.listIterator(l.size()));
  }
}
Adamski
la source
Cela me semble bien, bien que l'exposer comme une méthode statique au lieu d'un constructeur public éviterait (dans la plupart des cas) la nécessité pour le client de spécifier le paramètre de type. C'est comme ça que Guava fait ..
Kevin Bourrillion
2
Il s'agit de la meilleure implémentation, mais il ReverseIteratormanque le constructeur nécessaire et le code doit utiliser à la Listplace de ArrayList.
Andy
5

Si les listes sont assez petites pour que les performances ne soient pas un vrai problème, on peut utiliser le reverse-metod de la Listsclasse-in Google Guava. Donne un joli for-eachcode et la liste d'origine reste la même. De plus, la liste inversée est sauvegardée par la liste d'origine, donc toute modification apportée à la liste d'origine sera reflétée dans la liste inversée.

import com.google.common.collect.Lists;

[...]

final List<String> myList = Lists.newArrayList("one", "two", "three");
final List<String> myReverseList = Lists.reverse(myList);

System.out.println(myList);
System.out.println(myReverseList);

myList.add("four");

System.out.println(myList);
System.out.println(myReverseList);

Donne le résultat suivant:

[one, two, three]
[three, two, one]
[one, two, three, four]
[four, three, two, one]

Ce qui signifie que l'itération inverse de myList peut s'écrire:

for (final String someString : Lists.reverse(myList)) {
    //do something
}
Tobb
la source
5

Créez une coutume reverseIterable.

nanda
la source
Je ne sais pas pourquoi cela est voté, je pourrais simplement faire un wrapper pour la liste qui fait cela.
Allain Lalonde
Ce n'est pas moins propre que d'appeler Collections.reverse () à mon humble avis.
Adamski
@Allain: D'accord re. les downvotes bien que je ne vois pas pourquoi vous voyez un appel à listIterator (list.size ()) comme "pas propre". Même si vous l'enveloppez, vous devez toujours faire le même appel de méthode quelque part.
Adamski
Supposons que non, tout simplement pas disposé à prendre le coup de la performance, pour la propreté.
Allain Lalonde
18
Voté car je ne pense pas que cela soit une réponse complète.
Maarten Bodewes
3

Exemple très simple:

List<String> list = new ArrayList<String>();

list.add("ravi");

list.add("kant");

list.add("soni");

// Iterate to disply : result will be as ---     ravi kant soni

for (String name : list) {
  ...
}

//Now call this method

Collections.reverse(list);

// iterate and print index wise : result will be as ---     soni kant ravi

for (String name : list) {
  ...
}
Ravi Kant Soni
la source
2

On trouve également la méthode inverse des collections google .

Allain Lalonde
la source
Quelle version de google collection? votre lien n'existe plus.
AaA
1

Pour avoir un code qui ressemble à ceci:

List<Item> items;
...
for (Item item : In.reverse(items))
{
    ...
}

Mettez ce code dans un fichier appelé "In.java":

import java.util.*;

public enum In {;
    public static final <T> Iterable<T> reverse(final List<T> list) {
        return new ListReverseIterable<T>(list);
    }

    class ListReverseIterable<T> implements Iterable<T> {
        private final List<T> mList;

        public ListReverseIterable(final List<T> list) {
            mList = list;
        }

        public Iterator<T> iterator() {
            return new Iterator<T>() {
                final ListIterator<T> it = mList.listIterator(mList.size());

                public boolean hasNext() {
                    return it.hasPrevious();
                }
                public T next() {
                    return it.previous();
                }
                public void remove() {
                    it.remove();
                }
            };
        }
    }
}
intrepidis
la source
1
Cela a été mentionné ailleurs dans le fil, mais le listIteratorchamp doit être à l'intérieur de l' Iteratorimplémentation, pas l' Iterableimplémentation.
Andy
1
Pourquoi utiliser un type enum, pas une classe?
Aubin
1
Cela rend la classe 'In' non instanciable, sans avoir à écrire un constructeur par défaut privé. Bon pour les classes qui n'ont que des méthodes statiques.
intrepidis
Je dirais qu'abuser des énumérations juste pour éviter d'écrire un constructeur par défaut privé est au mieux source de confusion. Que diriez-vous d'utiliser les énumérations pour les énumérations et les classes pour les classes? En transformant une classe en énumération, elle obtient également implicitement une name()méthode, une ordinal()méthode et une static valueOf()méthode, par exemple.
JHH
1
Oui, vous pouvez continuer avec votre opinion et cela fonctionnera bien aussi, mais je pense le contraire. Les classes sont destinées à instancier des objets et à les abuser en incluant un constructeur privé par défaut pour empêcher leur instanciation est au mieux source de confusion. En Java, les énumérations sont en fait des classes, mais celles qui ne peuvent jamais être instanciées par conception.
intrepidis
0

Comme cela a été suggéré au moins deux fois, vous pouvez utiliser descendingIteratoravec un Deque, en particulier avec un LinkedList. Si vous souhaitez utiliser la boucle for-each (c'est-à-dire avoir un Iterable), vous pouvez construire et utiliser un wrapper comme celui-ci:

import java.util.*;

public class Main {

    public static class ReverseIterating<T> implements Iterable<T> {
        private final LinkedList<T> list;

        public ReverseIterating(LinkedList<T> list) {
            this.list = list;
        }

        @Override
        public Iterator<T> iterator() {
            return list.descendingIterator();
        }
    }

    public static void main(String... args) {
        LinkedList<String> list = new LinkedList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("E");

        for (String s : new ReverseIterating<String>(list)) {
            System.out.println(s);
        }
    }
}
masterxilo
la source
-10

Raison: "Je ne sais pas pourquoi il n'y a pas d'itérateur descendant avec ArrayList ..."

Étant donné que la liste de tableaux ne conserve pas la liste dans le même ordre que les données ont été ajoutées à la liste. Donc, n'utilisez jamais Arraylist.

La liste liée gardera les données dans le même ordre que AJOUTER à la liste.

Donc, ci-dessus dans mon exemple, j'ai utilisé ArrayList () afin de faire en sorte que l'utilisateur tord son esprit et lui fasse faire quelque chose de son côté.

Au lieu de cela

List<String> list = new ArrayList<String>();

UTILISATION:

List<String> list = new LinkedList<String>();

list.add("ravi");

list.add("kant");

list.add("soni");

// Iterate to disply : result will be as ---     ravi kant soni

for (String name : list) {
  ...
}

//Now call this method

Collections.reverse(list);

// iterate and print index wise : result will be as ---     soni kant ravi

for (String name : list) {
  ...
}
Ravi Kant Soni
la source
4
"Étant donné que la liste de tableaux ne conserve pas la liste dans le même ordre que les données ont été ajoutées à la liste" euh, oui, c'est le cas, au moins de la même manière qu'une liste liée. Pouvez-vous fournir un exemple de la façon dont ils ne le font pas?
corsiKa
Oui, ArrayList et LinkedList (le contrat List en général) gardent les éléments en insertion dans l'ordre. le contrat Set est non ordonné ou ordonné par tri (TreeSet). L'idée inverse n'est pas mauvaise, mais n'oubliez pas qu'elle réorganise en fait la liste, ce qui pourrait être lent.
Bill K
2
J'ai rétrogradé cette réponse car elle indique à tort que ArrayLists ne conserve pas les éléments dans l'ordre d'insertion. Comme le note @ bill-k, toutes les listes sont classées par collection. La suggestion d'utiliser à la place une LinkedList a du mérite, car elle comporte la méthode descendingIterator (), mais uniquement si les performances sont plus importantes que la mémoire et l'abstraction.
Michael Scheper