Можно ли сделать для каждого цикла в Java в обратном порядке?

148

Мне нужно запустить список в обратном порядке, используя Java.

Так, где это делает это вперед:

for(String string: stringList){
//...do something
}

Есть ли способ перебирать stringList в обратном порядке, используя для каждого синтаксиса?

Для ясности: я знаю, как перебирать список в обратном порядке, но хотел бы знать (ради любопытства), как это сделать для каждого стиля.

Рон Туффин
источник
5
Смысл цикла «для каждого» заключается в том, что вам просто нужно выполнить операцию с каждым элементом, и порядок не важен. For-each может обрабатывать элементы в совершенно случайном порядке, и он все равно будет делать то, для чего он предназначен. Если вам нужно обработать элементы определенным образом, я бы предложил сделать это вручную.
muusbolla
Библиотека коллекций Java. На самом деле не имеет ничего общего с языком. Виноват Джош Блох.
Том Хотин -
7
@muusbolla: Но так как список - это упорядоченная коллекция, то, конечно же, его порядок будет соблюден, независимо от того? Поэтому for-each не будет обрабатывать элементы списка в случайном порядке.
Ли Ковальковски
5
@muusbolla, это не правда. Может быть, в случае Setпроизводных коллекций. foreachгарантирует итерацию в порядке итератора, возвращаемого из iterator()метода коллекции. docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html
Роберт

Ответы:

151

Метод Collections.reverse фактически возвращает новый список с элементами оригинального списка, скопированными в него в обратном порядке, поэтому он имеет производительность O (n) по отношению к размеру исходного списка.

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

Например:

public class Reversed<T> implements Iterable<T> {
    private final List<T> original;

    public Reversed(List<T> original) {
        this.original = original;
    }

    public Iterator<T> iterator() {
        final ListIterator<T> i = original.listIterator(original.size());

        return new Iterator<T>() {
            public boolean hasNext() { return i.hasPrevious(); }
            public T next() { return i.previous(); }
            public void remove() { i.remove(); }
        };
    }

    public static <T> Reversed<T> reversed(List<T> original) {
        return new Reversed<T>(original);
    }
}

И вы бы использовали это как:

import static Reversed.reversed;

...

List<String> someStrings = getSomeStrings();
for (String s : reversed(someStrings)) {
    doSomethingWith(s);
}
натуральный
источник
22
Это в основном то, что делает Google Iterables.reverse, да :)
Джон Скит
10
Я знаю, что есть «правило», согласно которому мы должны принять ответ Джона :), но я хочу принять его (даже если они по сути одинаковы), потому что для этого не требуется, чтобы я включал другую стороннюю библиотеку (даже если некоторые могут утверждать, что эта причина нарушает одно из главных преимуществ ОО - возможность повторного использования).
Рон Туффин
Небольшая ошибка: в public void remove () не должно быть оператора return, это должно быть просто: i.remove ();
Джеспер
10
Collections.reverse () НЕ возвращает обратную копию, но действует в списке, передаваемом ему в качестве параметра. Как и ваше решение с итератором, хотя. Действительно элегантный
er4z0r
96

Для списка вы можете использовать библиотеку Google Guava :

for (String item : Lists.reverse(stringList))
{
    // ...
}

Обратите внимание, что это не переворачивает всю коллекцию и не делает ничего подобного - она ​​просто позволяет выполнять итерацию и произвольный доступ в обратном порядке. Это более эффективно, чем сначала изменить коллекцию.Lists.reverse

Чтобы отменить произвольную итерацию, вам нужно прочитать все и затем «переиграть» назад.

(Если вы не используете его, я тщательно рекомендую вам взглянуть на гуавы . Это отличный материал.)

Джон Скит
источник
1
Наша кодовая база активно использует обобщенную версию коллекций Commons, выпущенную larvalabs ( larvalabs.com/collections ). Просматривая репозиторий SVN для Apache Commons, становится ясно, что большая часть работы по выпуску java 5-версии Commons Collections выполнена, они просто еще не выпустили ее.
Скаффман
Мне это нравится. Если бы это не было так полезно, я бы назвал это штепселем.
geowa4
Интересно, почему Джакарта никогда не удосужилась обновить Apache Commons.
Ури
3
Они обновили это, вот что я говорю. Они просто не выпустили это.
Скаффман
23
Iterables.reverse устарел, используйте взамен Lists.reverse или ImmutableList.reverse.
Гарретт Холл
39

Список (в отличие от набора) представляет собой упорядоченную коллекцию, и ее повторение сохраняет порядок по контракту. Я бы ожидал, что стек будет повторяться в обратном порядке, но, к сожалению, это не так. Итак, самое простое решение, которое я могу придумать, это:

for (int i = stack.size() - 1; i >= 0; i--) {
    System.out.println(stack.get(i));
}

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

Collections.reverse () также выполняет эту работу, но обновляет список, а не возвращает копию в обратном порядке.

Гопи Редди
источник
3
Этот подход может быть подходящим для списков на основе массива (таких как ArrayList), но он будет неоптимальным для связанных списков, поскольку при каждом получении придется проходить список от начала до конца (или, возможно, от конца к началу) для каждого получения. Лучше использовать более умный итератор, как в решении Ната (оптимально для всех реализаций List).
Крис
1
Кроме того, это отклоняется от запроса в OP, который явно запрашивает for eachсинтаксис
Пол W
8

Это будет мешать исходному списку и также должно вызываться вне цикла. Кроме того, вы не хотите выполнять реверс каждый раз, когда делаете цикл - будет ли это верно, если один из них Iterables.reverse ideasбыл применен?

Collections.reverse(stringList);

for(String string: stringList){
//...do something
}
Филипп Гибб
источник
5

AFAIK, в стандартной библиотеке нет стандартного вида "reverse_iterator", который бы поддерживал синтаксис for-each, который уже является синтаксическим сахаром, который они привнесли в язык позднее.

Вы можете сделать что-то вроде для (Элемент Item: myList.clone (). Reverse ()) и заплатить соответствующую цену.

Это также вполне согласуется с явным явлением, заключающимся в отсутствии удобных способов выполнения дорогостоящих операций - поскольку список по определению может иметь O (N) сложность произвольного доступа (вы могли бы реализовать интерфейс с одиночной связью), наоборот итерация может в конечном итоге быть O (N ^ 2). Конечно, если у вас есть ArrayList, вы не платите эту цену.

Uri
источник
Вы можете запустить ListIterator в обратном направлении, который может быть заключен в итератор.
Том Хотин - снасть
@ Том: Хороший вопрос. Тем не менее, с помощью итератора вы все еще делаете надоедливый цикл для старого стиля, и вы все равно можете заплатить стоимость, чтобы добраться до последнего элемента для начала ... Я добавил квалификатор в своем ответе, хотя, спасибо.
Ури
У Deque есть обратный итератор.
Майкл Манси
2

Это может быть вариантом. Надеюсь, что есть лучший способ начать с последнего элемента, чем цикл while до конца.

public static void main(String[] args) {        
    List<String> a = new ArrayList<String>();
    a.add("1");a.add("2");a.add("3");a.add("4");a.add("5");

    ListIterator<String> aIter=a.listIterator();        
    while(aIter.hasNext()) aIter.next();

    for (;aIter.hasPrevious();)
    {
        String aVal = aIter.previous();
        System.out.println(aVal);           
    }
}
Кришна
источник
2

Что касается комментария : вы должны быть в состоянии использовать Apache CommonsReverseListIterator

Iterable<String> reverse 
    = new IteratorIterable(new ReverseListIterator(stringList));

for(String string: reverse ){
    //...do something
}

Как сказал @rogerdpack , вам нужно обернуть его ReverseListIteratorкак Iterable.

Serv-вкл
источник
1

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

Вы должны быть в состоянии сделать это в Java, создав собственную реализацию Iterable, которая будет возвращать элементы в обратном порядке.

Затем вы должны создать экземпляр оболочки (или вызвать метод what-have-you), который вернет реализацию Iterable, которая переворачивает элемент в каждом цикле.

casperOne
источник
1

Вам нужно будет отменить свою коллекцию, если вы хотите использовать для каждого синтаксиса из коробки и идти в обратном порядке.

Оуэн
источник
1

Все ответы, приведенные выше, только удовлетворяют требованию, либо заключая в оболочку другой метод, либо вызывая внешний код;

Вот решение, скопированное из Thinking in Java 4th edition , глава 11.13.1 AdapterMethodIdiom ;

Вот код:

// The "Adapter Method" idiom allows you to use foreach
// with additional kinds of Iterables.
package holding;
import java.util.*;

@SuppressWarnings("serial")
class ReversibleArrayList<T> extends ArrayList<T> {
  public ReversibleArrayList(Collection<T> c) { super(c); }
  public Iterable<T> reversed() {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return new Iterator<T>() {
          int current = size() - 1; //why this.size() or super.size() wrong?
          public boolean hasNext() { return current > -1; }
          public T next() { return get(current--); }
          public void remove() { // Not implemented
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }
}   

public class AdapterMethodIdiom {
  public static void main(String[] args) {
    ReversibleArrayList<String> ral =
      new ReversibleArrayList<String>(
        Arrays.asList("To be or not to be".split(" ")));
    // Grabs the ordinary iterator via iterator():
    for(String s : ral)
      System.out.print(s + " ");
    System.out.println();
    // Hand it the Iterable of your choice
    for(String s : ral.reversed())
      System.out.print(s + " ");
  }
} /* Output:
To be or not to be
be to not or be To
*///:~
Qiwen Li
источник
почему int current = size() - 1правильно? почему не int current = this.size() - 1илиint current = super.size() - 1
qiwen li
1

Работа вокруг:

Collections.reverse(stringList).forEach(str -> ...);

Или с гуавой :

Lists.reverse(stringList).forEach(str -> ...);
EssaidiM
источник
0

Определенно поздний ответ на этот вопрос. Одной из возможностей является использование ListIterator в цикле for. Это не так чисто, как синтаксис двоеточия, но это работает.

List<String> exampleList = new ArrayList<>();
exampleList.add("One");
exampleList.add("Two");
exampleList.add("Three");

//Forward iteration
for (String currentString : exampleList) {
    System.out.println(currentString); 
}

//Reverse iteration
for (ListIterator<String> itr = exampleList.listIterator(exampleList.size()); itr.hasPrevious(); /*no-op*/ ) {
    String currentString = itr.previous();
    System.out.println(currentString); 
}

Кредит на синтаксис ListIterator идет в «Пути перебора списка в Java»

ScottMichaud
источник