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.


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

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

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.


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

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...


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

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

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
}

5

Crie um costume reverseIterable.


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



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

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

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.