Перебор списка в обратном порядке в Java


251

Я перемещаю кусок кода, чтобы использовать дженерики. Одним из аргументов для этого является то, что цикл for гораздо чище, чем отслеживание индексов или использование явного итератора.

Примерно в половине случаев список (ArrayList) итерируется в обратном порядке с использованием индекса сегодня.

Может кто-нибудь предложить более чистый способ сделать это (так как мне не нравится indexed for loopпри работе с коллекциями), хотя это работает?

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

Примечание: я не могу добавить какие-либо новые зависимости вне JDK.


10
Что плохого в использовании явного индекса для итерации по индексированной структуре данных? По крайней мере, он показывает вам, что именно происходит. Для итерации в обратном направлении я всегда придерживаюсь несколько более короткой идиомы:for (int i = nodes.size(); --i >= 0;)
x4u

4
Ничего особенного, я бы просто запрограммировал интерфейс и не знал о том, какой список я использую. Хотя мне очень нравится твоя короткая рука. (+1 комментарий)
Аллен Лалонд

1
@ x4u: в этом нет ничего особенного, хотя итератор работает быстро, а также позволяет легко удалять элементы во время итерации.
Адамски

2
Этот класс не работает, потому что пользователь может захотеть повторить итерацию во второй раз по одному и тому же Итерируемому, или список может измениться между тем, когда итерируемое конструируется и когда оно итерируется. Я уверен, что для ваших целей вы просто позаботитесь об этом, но исправить код будет несложно; или просто украдите
Кевин Бурриллион

1
Справедливо, но даже Guava один восприимчив к тому же, если я правильно читаю. Если бы пользователь сохранил копию результата от обратного, у него возникла бы та же проблема.
Аллен Лалонд

Ответы:


447

Попробуй это:

// 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
Неплохо. Не использует индекс, но теряет элегантность для каждого синтаксиса. +1 в любом случае.
Аллен Лалонд

26
Вызов listIterator () без аргумента индекса даст итератор в начале списка, поэтому hasPrevious () вернет false при первом вызове.
Адамски

2
Вы хотите индекс на listIteratorзвонке, я думаю.
Том Хотин - Tackline

1
Вы можете написать, Iteratorчто использует ListIteratorв обратном порядке, но это может не стоить одного цикла.
Том Хотин - Tackline

1
Это не одна петля, поэтому я взял это и завернул. pastebin.ca/1759041 так что теперь я могу это сделатьfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde

35

Гуава предлагает Lists#reverse(List)и ImmutableList#reverse(). Как и в большинстве случаев для Гуавы, первый делегирует второму, если аргумент является аргументом ImmutableList, так что вы можете использовать первый во всех случаях. Они не создают новые копии списка, а просто «обращают взгляды» на него.

пример

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

23

Я не думаю, что это возможно, используя синтаксис цикла for. Единственное, что я могу предложить, это сделать что-то вроде:

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

... но я бы не сказал, что это "чище", учитывая, что оно будет менее эффективным.


26
и это также меняет список, через который вы проходите, что является огромным побочным эффектом. (Скажем, вы заключаете это в метод, каждый раз, когда он вызывается, вы пересекаете список другим способом ^^)
jolivier

Это самое чистое решение.
jfajunior

14

Вариант 1. Задумывались ли вы о реверсе списка с коллекциями # reverse () и последующем использовании foreach?

Конечно, вы также можете реорганизовать свой код так, чтобы список был упорядочен правильно, чтобы вам не пришлось его менять, что требует дополнительного пространства / времени.


РЕДАКТИРОВАТЬ:

Вариант 2: в качестве альтернативы, вы могли бы использовать Deque вместо ArrayList? Это позволит вам перебирать вперед и назад


РЕДАКТИРОВАТЬ:

Вариант 3: Как предлагали другие, вы можете написать Итератор, который будет проходить по списку в обратном порядке, вот пример:

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
Подумал об этом, но там стоимость реверса это непомерно. Кроме того, такая итерация требует только половину времени. Если щелкнуть по нему, проблема переместится на другую половину.
Аллен Лалонд

3
+ для Deque; Типичные реализации имеют descendingIterator().
trashgod

Это будет ужасно для связанных списков, вариант ОП лучше.
masterxilo

1
Использование for eachвыражения является наиболее идиоматическим решением на мой взгляд. Приятно осознавать, что это возможно, если ваш список реализует Iterable таким образом, что он повторяется в обратном направлении. Я собираюсь использовать этот подход и использовать класс ReverseListIterator из коллекций Apache Commons.
Дэвид Грумс

11

Вы можете использовать конкретный класс LinkedListвместо общего интерфейса List. Тогда у вас есть descendingIteratorдля итерации с обратным направлением.

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

Не знаю, почему нет descendingIteratorс ArrayList...


9

Это старый вопрос, но ему не хватает java8-дружественного ответа. Вот несколько способов перебрать список с помощью Streaming API:

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
очень красиво, просто косметическое изменение: int size = list.size (); ListIterator <Integer> it = list.listIterator (размер); Stream.generate (это :: предыдущая) .limit (размер) .forEach (System.out :: Println);
Benez

5

Вот (непроверенная) реализация ReverseIterable. Когда iterator()вызывается он создает и возвращает личное ReverseIteratorвыполнение, которое просто отображает вызовы на hasNext()на hasPrevious()и вызовы next()сопоставленные с previous(). Это означает, что вы можете выполнить итерацию ArrayListв обратном порядке следующим образом:

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

Определение класса

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

Выглядит правильно для меня, хотя выставление его как статический метод вместо публичного конструктора (в большинстве случаев) позволит избежать необходимости указывать параметром типа клиента. Вот как это делает
Гуава

2
Это лучшая реализация, однако ReverseIteratorотсутствует необходимый конструктор, и код следует использовать Listвместо ArrayList.
Энди

5

Если списки довольно малы, так что производительность не является реальной проблемой, можно использовать метод reverse-class Listsв Google Guava. for-eachПолучает довольно -код, и оригинальный список остается прежним. Кроме того, обратный список поддерживается исходным списком, поэтому любые изменения исходного списка будут отражены в обратном.

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

Дает следующий результат:

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

Это означает, что обратная итерация myList может быть записана как:

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

5

Создать кастом reverseIterable.


Не знаю, почему за это проголосовали, я мог бы просто сделать обертку для списка, который делает это.
Аллен Лалонд

Это не менее чисто, чем вызов Collections.reverse () IMHO.
Адамски

@ Аллен: Согласен хотя я не понимаю, почему вы рассматриваете вызов listIterator (list.size ()) как "не чистый". Даже если вы обернетесь, вам все равно придется где-то сделать тот же вызов метода.
Адамски

Предположим, нет, просто не желаю принять удар производительности за чистоту.
Аллен Лалонд

18
Отвергнут, поскольку я не думаю, что это полный ответ.
Maarten Bodewes

3

Очень простой пример:

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

Чтобы иметь код, который выглядит так:

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

Поместите этот код в файл с именем «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
Это было упомянуто в другом месте в потоке, но listIteratorполе должно быть внутри Iteratorреализации, а не Iterableреализации.
Энди

1
Почему используется тип enum, а не класс?
Обин

1
Это делает класс «In» неинстанцируемым, без необходимости писать частный конструктор по умолчанию. Хорошо для классов, которые имеют только статические методы.
Intrepidis

Я бы сказал, что злоупотребление перечислениями просто для того, чтобы избежать написания частного конструктора по умолчанию, в лучшем случае сбивает с толку. Как насчет использования перечислений для перечислений и классов для классов? Превращая класс в перечисление, он также неявно получает name()метод, ordinal()метод и static valueOf()метод, например.
JHH

1
Да, вы можете продолжить с вашим мнением, и это тоже будет работать, но я думаю, что наоборот. Классы предназначены для создания экземпляров объектов и злоупотребления ими путем включения частного конструктора по умолчанию, чтобы запретить их создание, в лучшем случае сбивают с толку. В Java перечисления на самом деле являются классами, но те, которые никогда не могут быть созданы в дизайне.
Intrepidis

0

Как было предложено, по крайней мере, дважды, вы можете использовать descendingIteratorс Deque, в частности с LinkedList. Если вы хотите использовать цикл for-each (то есть иметь an Iterable), вы можете создать и использовать обертку следующим образом:

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

Причина: «Не знаю, почему нет никакого нисходящего Итератора с ArrayList ...»

Поскольку список массивов не сохраняет список в том же порядке, в котором данные были добавлены в список. Поэтому никогда не используйте Arraylist.

Связанный список будет хранить данные в том же порядке ДОБАВИТЬ в список.

Итак, выше, в моем примере, я использовал ArrayList (), чтобы заставить пользователя крутить свои мысли и заставлять их тренировать что-то со своей стороны.

Вместо этого

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

ОБЛАСТЬ ПРИМЕНЕНИЯ:

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
«Поскольку список массивов не сохраняет список в том же порядке, в котором данные были добавлены в список», да, это так, по крайней мере, так же, как это делает связанный список. Можете ли вы привести пример того, как они этого не делают?
CorsiKa

Да, ArrayList и LinkedList (контракт List в целом) поддерживают вставку элементов в порядке. Контракт набора неупорядочен или упорядочен по типу (TreeSet). Обратная идея неплоха, но помните, что она на самом деле переупорядочивает список, который может быть медленным.
Билл К

2
Я отклонил этот ответ, потому что он ошибочно утверждает, что ArrayLists не поддерживают элементы в порядке вставки. Как отмечает @ bill-k, все списки являются упорядоченными коллекциями по определению. Предложение использовать LinkedList вместо этого имеет свои достоинства, поскольку в нем есть метод downndingIterator (), но только в том случае, если производительность важнее, чем память и абстракция.
Майкл Шепер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.