Iterando uma Lista em Ordem Inversa em Java

251

Estou migrando um pedaço de código para fazer uso de genéricos. Um argumento para fazer isso é que o loop for é muito mais limpo do que controlar os índices ou usar um iterador explícito.

Em cerca de metade dos casos, a lista (um ArrayList) está sendo iterada na ordem inversa, usando um índice hoje.

Alguém pode sugerir uma maneira mais limpa de fazer isso (já que eu não gosto indexed for loopquando trabalho com coleções), embora funcione?

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

Nota: Não consigo adicionar novas dependências fora do JDK.

Allain Lalonde
fonte
10
O que há de tão ruim em usar um índice explícito para iterar em uma estrutura de dados indexados? Pelo menos, mostra o que exatamente está acontecendo. Para iterar para trás, sempre tenho o seguinte idioma um pouco mais curto:for (int i = nodes.size(); --i >= 0;)
x4u
4
Nada em particular, eu prefiro programar para uma interface e não saber sobre o tipo de lista que estou usando. Embora eu goste muito da sua mão curta. (+1 comentário)
Allain Lalonde
1
@ x4u: Não há muito, embora o Iterator seja à prova de falhas e também permita que os elementos sejam facilmente removidos durante a iteração.
Adamski
2
Essa classe está quebrada, porque o usuário pode querer repetir uma segunda vez na mesma iterável ou a lista pode mudar entre quando a iterável é construída e quando é iterada. Tenho certeza de que, para seus propósitos, você fará o possível, mas não seria muito difícil corrigir o código; ou apenas roube o código do Guava (licença Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion
1
É justo, mas até a goiaba é suscetível à mesma coisa se eu estiver lendo direito. Se um usuário mantivesse uma cópia do resultado inversa, ele teria o mesmo problema.
Allain Lalonde

Respostas:

447

Tente o seguinte:

// 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
fonte
4
Não é ruim. Não usa o índice, mas perde a elegância do para cada sintaxe. +1 de qualquer maneira.
Allain Lalonde
26
Chamar listIterator () sem um argumento de índice fornecerá um Iterator no início da lista e, portanto, hasPrevious () retornará false na primeira chamada.
Adamski
2
Você quer um índice na listIteratorligação, eu acho.
Tom Hawtin - defina
1
Você pode escrever um Iteratorque use um ListIteratorno sentido inverso, mas que pode não valer a pena para um loop.
Tom Hawtin - defina
1
Não é um loop, então peguei e envolvi. pastebin.ca/1759041 então, agora eu posso fazerfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde
35

GoiabaLists#reverse(List) e ofertas ImmutableList#reverse(). Como na maioria dos casos para o Goiaba, o primeiro delega para o último se o argumento for um ImmutableList, então você pode usá-lo em todos os casos. Isso não cria novas cópias da lista, mas apenas "visões invertidas" dela.

Exemplo

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

Eu não acho que é possível usar a sintaxe do loop for. A única coisa que posso sugerir é fazer algo como:

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

... mas eu não diria que isso é "mais limpo", uma vez que será menos eficiente.

Adamski
fonte
26
e também altera a lista que você percorre, o que é um efeito colateral enorme. (Diga você enrolar isso em um método, cada vez que é chamado você percorrer a lista para o outro lado ^^)
jolivier
Esta é a solução mais limpa.
jfajunior 11/03
14

Opção 1: Você pensou em reverter a lista com coleções # reverse () e depois usar o foreach?

Obviamente, você também pode refatorar seu código, de modo que a lista seja ordenada corretamente, para que você não precise revertê-lo, o que utiliza espaço / tempo extra.


EDITAR:

Opção 2: Como alternativa, você poderia usar um Deque em vez de um ArrayList? Permite iterar para frente e para trás


EDITAR:

Opção 3: Como outros sugeriram, você pode escrever um Iterador que percorrerá a lista ao contrário, e aqui está um exemplo:

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
fonte
1
Já pensou nisso, mas o custo de revertê-lo é proibitivo. Além disso, requer apenas esse tipo de iteração cerca de metade do tempo. Virá-lo apenas moveria o problema para a outra metade.
Allain Lalonde
3
+ para Deque; implementações típicas têm descendingIterator().
trashgod
Isso seria péssimo para listas vinculadas, a variante do OP é melhor.
Masterxilo 26/05
1
Usar uma for eachexpressão é a solução mais idiomática na minha opinião. É bom perceber que isso é possível se a sua Lista implementar o Iterable de uma maneira que itere para trás. Vou usar essa abordagem e usar a classe ReverseListIterator da Apache Commons Collections.
David Groomes
11

Você poderia usar a classe concreta em LinkedListvez da interface geral List. Então você tem um descendingIteratorpara iterar com a direção reversa.

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

Não sei por que não há descendingIteratorcom ArrayList...

tangens
fonte
9

Esta é uma pergunta antiga, mas está faltando uma resposta amigável para java8. Aqui estão algumas maneiras de reverter a iteração da lista, com a ajuda da API de 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
fonte
2
muito bom, apenas uma mudança cosmética: int size = list.size (); ListIterator <Integer> it = list.listIterator (tamanho); Stream.generate (it :: previous) .limit (size) .forEach (System.out :: println);
Benez
5

Aqui está uma implementação (não testada) de a ReverseIterable. Quando iterator()é chamado, ele cria e retorna uma ReverseIteratorimplementação privada , que simplesmente mapeia as chamadas hasNext()para hasPrevious()e as chamadas para next()são mapeadas previous(). Isso significa que você pode iterar um ArrayListinverso da seguinte maneira:

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

Definição 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
fonte
Parece certo para mim, embora expô-lo como um método estático em vez de um construtor público evitaria (na maioria dos casos) a necessidade de o cliente especificar o parâmetro de tipo. Isto é como goiaba faz isso ..
Kevin Bourrillion
2
Essa é a melhor implementação, porém ReverseIteratorestá faltando o construtor necessário e o código deve ser usado em Listvez de ArrayList.
266 Andy Andy
5

Se as listas forem razoavelmente pequenas para que o desempenho não seja um problema real, é possível usar o reversemétodo-da Listsclasse-in Google Guava. for-eachProduz um código bonito e a lista original permanece a mesma. Além disso, a lista invertida é apoiada pela lista original, portanto, qualquer alteração na lista original será refletida na lista invertida.

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

Rende o seguinte resultado:

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

O que significa que a iteração reversa do myList pode ser escrita como:

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

Crie um costume reverseIterable.

Nanda
fonte
Não sei por que isso está sendo votado, talvez eu faça um invólucro para a lista que faz isso.
Allain Lalonde
Isso não é menos limpo do que chamar Collections.reverse () IMHO.
Adamski
@ Allain: concordou re. os votos negativos, embora eu não entenda por que você visualiza uma chamada para listIterator (list.size ()) como "não limpa". Mesmo se você o envolver, ainda terá que fazer a mesma chamada de método em algum lugar.
Adamski
Suponha que não, apenas que não esteja disposto a aceitar o desempenho, para limpeza.
Allain Lalonde
18
Votado como não acho que isso seja uma resposta completa.
Maarten Bodewes
3

Exemplo muito simples:

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
fonte
2

Também encontrou o método reverso das coleções do google .

Allain Lalonde
fonte
Qual versão da coleção do Google? seu link não existe mais.
AaA
1

Para ter um código parecido com este:

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

Coloque esse código em um arquivo chamado "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
fonte
1
Isso foi mencionado em outra parte do encadeamento, mas o listIteratorcampo precisa estar dentro da Iteratorimplementação, não na Iterableimplementação.
265 Andy Andy
1
Por que usar um tipo de enumeração, não uma classe?
Aubin
1
Isso torna a classe 'In' inconstante, sem a necessidade de escrever um construtor padrão privado. Bom para classes que possuem apenas métodos estáticos.
Intrepidis
Eu diria que abusar de enumerações apenas para evitar escrever um construtor padrão privado é, na melhor das hipóteses, confuso. Que tal usar enums para enums e classes para classes? Ao transformar uma classe em enumeração, ele também obtém implicitamente um name()método, um ordinal()método e um static valueOf()método, por exemplo.
JHH
1
Sim, você pode continuar com sua opinião e isso também funcionará bem, mas penso o contrário. As classes são para instanciar objetos e abusar deles, incluindo um construtor padrão privado para inibir sua instanciação, na melhor das hipóteses. Em Java, enums são na verdade classes, mas nunca podem ser instanciadas pelo design.
Intrepidis
0

Como foi sugerido pelo menos duas vezes, você pode usar descendingIteratorcom a Deque, em particular com a LinkedList. Se você quiser usar o loop for-each (ou seja, possuir um Iterable), poderá construir e usar um wraper como este:

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
fonte
-10

Razão: "Não sei por que não há descendingIterator com ArrayList ..."

Como a lista de matrizes não mantém a lista na mesma ordem em que os dados foram adicionados à lista. Portanto, nunca use Arraylist.

A lista vinculada manterá os dados na mesma ordem de ADD para listar.

Então, acima no meu exemplo, usei ArrayList () para fazer com que o usuário torça a cabeça e faça com que exercite algo do lado deles.

Em vez disso

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

USAR:

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
fonte
4
"Como a lista de matrizes não mantém a lista na mesma ordem em que os dados foram adicionados à lista" umm, sim, sim, pelo menos da mesma maneira que uma lista vinculada. Você pode fornecer um exemplo de como eles não o fazem?
precisa saber é o seguinte
Sim, ArrayList e LinkedList (o contrato Lista em geral) mantêm os itens inseridos em ordem. o contrato Set não é ordenado ou ordenado por classificação (TreeSet). A idéia inversa não é ruim, mas lembre-se de que ela realmente reordena a lista, que pode ser lenta.
Bill K
2
Eu diminuí a votação desta resposta porque afirma erroneamente que ArrayLists não mantêm itens em ordem de inserção. Como observa @ bill-k, todas as listas são coleções ordenadas por definição. A sugestão de usar um LinkedList tem mérito, pois apresenta o método descendingIterator (), mas apenas se o desempenho for uma preocupação maior do que a memória e a abstração.
Michael Scheper