2009-07-08 2 views
122

Мне нужно запустить список в обратном порядке с помощью Java.Можно ли сделать каждый цикл в java в обратном порядке?

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

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

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

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

+3

Как ни странно, я прочитал этот вопрос частично через чтение раздела книги Scala, которая занимается неизменными операциями списка ... хорошей демонстрация отсутствия выразительной силы в Ява. – skaffman

+5

Точка цикла «для каждого» заключается в том, что вам просто нужно выполнить операцию над каждым элементом, а порядок не важен.Каждый из них мог обрабатывать элементы в совершенно случайном порядке, и он все равно будет делать то, для чего он был предназначен. Если вам нужно обработать элементы определенным образом, я бы предложил сделать это вручную. – muusbolla

+0

Библиотека коллекций Java. На самом деле это не имеет никакого отношения к языку. Вина Джош Блох. –

ответ

129

Не используйте метод Collections.reverse он фактически переворачивает оригинальный список на месте. Если вы используете его:

Неправильный путь!

Collections.reverse(new ArrayList(stringList))

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

В качестве более эффективного решения вы можете написать класс, который представляет собой обратный вид списка как универсального типа Iterable. Итератор, возвращенный вашим классом, будет использовать 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); 
} 
+17

Это в основном то, что делает Iterables.reverse от Google, да :) –

+9

Я знаю, что есть «правило», которое мы должны принять ответ Джона :), но .. Я хочу принять этот (хотя они по сути то же самое), потому что он не требует, чтобы я включил другую стороннюю библиотеку (хотя некоторые могли утверждать, что эта причина нарушает одно из главных преимуществ OO - повторное использование). –

+0

Небольшая ошибка: в public void remove() не должно быть оператора return, это должно быть просто: i.remove(); – Jesper

4

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

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

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

+0

Вы можете запустить ListIterator назад, который можно обернуть внутри Iterator. –

+0

@ Тома: Хорошая точка. Однако с итератором вы все еще делаете раздражающий стиль старого цикла, и вы все равно можете заплатить стоимость, чтобы добраться до последнего элемента, чтобы начать ... Я добавил квалификатор в свой ответ, хотя, спасибо. – Uri

+0

У Deque есть обратный итератор. –

1

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

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

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

1

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

84

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

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

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

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

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

+1

Наша кодовая база активно использует расширенную версию Commons Collections, выпущенную larvalabs (http://larvalabs.com/collections/). Просматривая репо SVN для Apache Commons, становится ясно, что большая часть работы по выпуску версии Commons Collection для Java версии 5 завершена, но они еще не выпустили ее. – skaffman

+0

Мне это нравится. Если бы это было не так полезно, я бы назвал это плагин. – geowa4

+0

Интересно, почему Джакарта никогда не беспокоилась об обновлении Apache Commons. – Uri

8

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

Collections.reverse(stringList); 

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

Это может быть вариант. Надеюсь, что есть лучший способ начать с последнего элемента, чем до цикла 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);   
    } 
} 
27

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

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

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

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

+2

Этот подход может быть точным для списков на основе массива (таких как ArrayList), но он будет субоптимальным для Linked Lists, так как каждый get должен будет проходить список от начала до конца (или, возможно, заканчиваться до начала) для каждого get. Лучше использовать более интеллектуальный итератор, как в решении Nat (оптимальный для всех реализаций List). – Chris

+1

Кроме того, он отклоняется от запроса в OP, который явно запрашивает синтаксис 'for each' –

1

По the comment: Вы должны быть в состоянии использовать Apache Commons ReverseListIterator

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

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

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

+1

К сожалению, это возвращает« Итератор »вместо« Итерабельный », поэтому он не может быть использован в цикле foreach: | – rogerdpack

+0

@rogerdpack: Спасибо. См. Обновление для обходного пути. –

0

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

Вот решение скопировано из мышления в Java 4-е издание, глава 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 
*///:~ 
+0

Почему 'int current = size() - 1' право? почему бы не 'int current = this.size() - 1' или' int current = super.size() - 1' –

Смежные вопросы