2010-01-20 13 views
197

Sto migrando un pezzo di codice per fare uso di generici. Un argomento per farlo è che il ciclo for è molto più pulito di tenere traccia degli indici, o usare un iteratore esplicito.Iterazione di un elenco in ordine inverso in java

In circa la metà dei casi, l'elenco (un oggetto ArrayList) viene iterato in ordine inverso utilizzando un indice oggi.

Qualcuno può suggerire un modo più pulito di farlo (poiché non mi piace il indexed for loop quando si lavora con le raccolte), sebbene funzioni?

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

Nota: Non posso aggiungere eventuali nuove dipendenze di fuori del JDK.

+10

Cosa c'è di così male nell'usare un indice esplicito per iterare su una struttura di dati indicizzati? Almeno ti mostra cosa sta succedendo esattamente. Per l'iterazione all'indietro ho sempre il seguente idioma leggermente più breve: 'for (int i = nodes.size(); --i> = 0;)' – x4u

+4

Niente di particolarmente, preferirei semplicemente programmare su un'interfaccia e non sapere cosa tipo di lista che sto usando. Anche se mi piace molto la tua mano corta. (+1 commento) –

+1

@ x4u: Non c'è molto, anche se Iterator è fail-fast e consente anche di rimuovere facilmente gli elementi durante l'iterazione. – Adamski

risposta

368

Prova questo:

// 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()); 
} 
+2

Non male. Non usa l'indice, ma perde l'eleganza di ogni sintassi. +1 comunque. –

+2

Vuoi un indice sulla chiamata 'listIterator', penso. –

+20

Chiamare listIterator() senza un argomento index darà un Iterator all'inizio della lista e quindi haPrevious() restituirà false alla prima chiamata. – Adamski

7

Creare un'Opzione 1 personalizzato reverseIterable

+0

Non so perché questo sta diminuendo il voto, potrei semplicemente creare un wrapper per la lista che fa questo. –

+0

Questo non è meno pulito di chiamare Collections.reverse() IMHO. – Adamski

+0

@Allain: concordato. i downvotes anche se non vedo perché si visualizza una chiamata a listIterator (list.size()) come "not clean". Anche se lo avvolgi, devi comunque chiamare lo stesso metodo da qualche parte. – Adamski

12

: Avete pensato di invertire la lista con Collections#reverse() e quindi utilizzando foreach?

Naturalmente, si può anche volere refactoring il codice in modo che l'elenco è ordinato correttamente in modo da non dover invertire, che utilizza spazio/tempo extra.


EDIT:

Opzione 2: In alternativa, si potrebbe utilizzare una Deque al posto di un ArrayList? Essa vi permetterà di eseguire iterazioni in avanti e indietro


EDIT:

Opzione 3: Come altri hanno suggerito, si potrebbe scrivere un Iterator che passare attraverso la lista in ordine inverso, ecco un esempio:

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); 
} 
+1

Ci hai pensato, ma il costo per l'inversione è proibitivo. Inoltre, richiede solo questo tipo di iterazione circa metà del tempo. Capovolgerlo sposterebbe il problema solo verso l'altra metà. –

+1

+ per Deque; le implementazioni tipiche hanno 'descendingIterator()'. – trashgod

+0

Questo comporterebbe un errore per gli elenchi collegati, la variante dell'OP è migliore. – masterxilo

20

Non penso sia possibile utilizzare la sintassi del ciclo for. L'unica cosa che posso suggerire è quello di fare qualcosa di simile:

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

... ma non direi che questo è "pulito" dato che sta per essere meno efficiente.

+18

e cambia anche la lista che attraversi, il che è un enorme effetto collaterale. (Supponete di avvolgere questo in un metodo, ogni volta che viene chiamato si attraversa l'elenco nell'altro modo ^^) – jolivier

4

Ecco un (non testata) attuazione di un ReverseIterable. Quando viene chiamato iterator(), viene creata e restituita un'implementazione privata ReverseIterator, che associa semplicemente le chiamate a hasNext() a hasPrevious() e le chiamate a next() vengono associate a previous(). Significa che potrebbe iterare su un ArrayList in senso inverso come segue:

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

Classe Definizione

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())); 
    } 
} 
+0

Sembra giusto per me, sebbene esporlo come metodo statico invece di un costruttore pubblico (nella maggior parte dei casi) eviterebbe la necessità che il client specifichi il parametro type. Questo è il modo in cui Guava lo fa .. –

+1

Questa è la migliore implementazione, tuttavia 'ReverseIterator' manca il costruttore necessario e il codice dovrebbe usare' List' invece di 'ArrayList'. – Andy

2

trovato anche collezioni di Google reverse metodo.

+0

Quale versione della raccolta google? il tuo link non esiste più. – AaA

8

È possibile utilizzare la classe di calcestruzzo LinkedList anziché l'interfaccia generale List.Quindi hai un descendingIterator per iterare con la direzione inversa.

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

Non so perché non v'è alcun descendingIterator con ArrayList ...

3

esempio molto semplice:

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) { 
    ... 
} 
-7

Motivo: "Non so perché non v'è alcun descendingIterator con ArrayList ..."

Dal lista di array doesnot di tenere l'elenco nello stesso ordine come dati è stato aggiunto alla lista. Quindi, non usare mai Arraylist.

L'elenco collegato manterrà i dati nello stesso ordine di AGGIUNGI alla lista.

Così, soprattutto nel mio esempio, ho usato ArrayList() al fine di rendere all'utente di torcere la loro mente e renderli di allenamento qualcosa da parte loro.

Invece di questo

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

USO:

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) { 
    ... 
} 
+4

"Dato che l'elenco di array non mantiene l'elenco nello stesso ordine in cui i dati sono stati aggiunti all'elenco" umm, sì, lo fa almeno nello stesso modo di un elenco collegato. Puoi fornire un esempio di come non lo fanno? – corsiKa

+0

Sì, ArrayList e LinkedList (il contratto Elenco in generale) mantiene gli articoli in inserimento in ordine. il contratto Set non è ordinato o ordinato per tipo (TreeSet). L'idea inversa non è male, ma ricorda che in realtà riordina la lista che potrebbe essere rallentata. –

+2

Ho downvoted questa risposta perché afferma erroneamente che ArrayLists non mantiene gli articoli nell'ordine di inserimento. Come note @ bill-k, tutte le liste sono raccolte ordinate per definizione. Il suggerimento di utilizzare una LinkedList ha invece il merito, dal momento che presenta il metodo descendingIterator(), ma solo se le prestazioni sono una preoccupazione più grande della memoria e dell'astrazione. –

2

avere il codice che assomiglia a questo:

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

Mettete questo codice in un file chiamato "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(); 
       } 
      }; 
     } 
    } 
} 
+1

Questo è stato menzionato altrove nel thread, ma il campo 'listIterator' deve essere all'interno dell'implementazione di' Iterator', non dell'implementazione di 'Iterable'. – Andy

+0

Aggiornato con la mia versione 'finale'. –

+1

Perché utilizzare un tipo enum, non una classe? – Aubin

24

Guava offerte Lists#reverse(List) e ImmutableList#reverse(). Come nella maggior parte dei casi per Guava, l'ex delegato a quest'ultimo se l'argomento è uno ImmutableList, quindi è possibile utilizzare il primo in tutti i casi. Questi non creano nuove copie della lista ma solo "viste invertite" di essa.

Esempio

List reversed = ImmutableList.copyOf(myList).reverse(); 
0

Come è stato suggerito almeno due volte, è possibile utilizzare descendingIterator con una Deque, in particolare con a LinkedList. Se si desidera utilizzare il ciclo for-each (vale a dire, hanno un Iterable), è possibile costruire e utilizzare un wraper come questo:

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); 
     } 
    } 
} 
4

Se le liste sono abbastanza piccolo in modo che le prestazioni non è un problema reale, uno può usare il reverse -metod della classe Lists in Google Guava. Rende piuttosto il codice for-each e l'elenco originale rimane lo stesso. Inoltre, l'elenco invertito è supportato dall'elenco originale, quindi qualsiasi modifica all'elenco originale si rifletterà in quella invertita.

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); 

produce il seguente risultato:

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

Il che significa che l'iterazione inversa di myList può essere scritta come:

for (final String someString : Lists.reverse(myList)) { 
    //do something 
} 
4

Questa è una vecchia questione, ma sembra che manchi un java8- risposta amichevole. Ecco alcuni modi per invertire l'iterazione dell'elenco, con l'aiuto dell'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 

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

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

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

// If list is RandomAccess (i.e. an ArrayList) 
IntStream.range(0, list.size()).boxed().sorted(Comparator.reverseOrder()) 
    .map(list::get).forEach(System.out::println); // 5 7 3 3 1 
+2

molto bello, solo una modifica estetica: int size = list.size(); ListIterator it = list.listIterator (dimensione); Stream.generate (it :: previous) .limit (size) .forEach (System.out :: println); – benez

Problemi correlati