Ітерація через список у зворотному порядку в 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: У ньому не так багато, хоча Iterator не працює, а також дозволяє легко видалити елементи під час ітерації.
Адамські

2
Цей клас порушений, тому що користувач може захотіти повторити повторення над тим же Iterable, або список може змінюватися між тим, коли будується ітерабельний і коли він ітераційний. Я впевнений, що для ваших цілей ви просто переконайтеся, що цього не зробите, але виправити код було б не надто важко; або просто вкрасти код у Guava (ліцензія Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Кевін Боррліон

1
Досить справедливо, але навіть гуава чутливий до того ж, якщо я правильно його читаю. Якби користувач зберігав копію результату від зворотного, це матиме ту саму проблему.
Аллайн Лалонде

Відповіді:


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 () поверне помилковий при першому виклику.
Адамські

2
Ви хочете, щоб індекс listIteratorвиклику, я думаю.
Том Хотін - тайклін

1
Ви можете написати анкету, Iteratorяка використовує ListIteratorзворотний бік, але це може бути не варте одного циклу.
Том Хотін - тайклін

1
Це не одна петля, тому я взяв це і завернув. pastebin.ca/1759041 так, тепер я можу зробитиfor (Node each : new ListReverse<Node>(nodes)) { }
Аллайн Лалонда

35

Гуава пропонує Lists#reverse(List)і ImmutableList#reverse(). Як і в більшості випадків для Guava, колишні делегують останнім, якщо аргумент є аргументом ImmutableList, тож ви можете використовувати перший у всіх випадках. Вони не створюють нових копій списку, а лише "перевернуті перегляди" його.

Приклад

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

23

Я не думаю, що це можливо за допомогою синтаксису для циклу. Єдине, що я можу запропонувати - це зробити щось на кшталт:

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
+ для Деке; типові реалізації descendingIterator().
trashgod

Це могло б виглядати жахливо для пов'язаних списків, варіант ОП - кращий.
masterxilo

1
Використання for eachвиразу - на мою думку, найбільш ідіоматичне рішення. Приємно усвідомити, що це можливо, якщо ваш Список реалізує Iterable таким чином, що він повторює назад. Я буду використовувати цей підхід і використовувати клас ReverseListIterator з Apache Commons Collections.
David Groomes

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-metod -класу 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.


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

Це не менш чисто, ніж викликати IMGO Collections.reverse ().
Адамські

@Allain: Погоджений повтор. знижки, хоча я не розумію, чому ви розглядаєте виклик 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, а не клас?
Aubin

1
Це робить клас "В" не миттєвим, без необхідності писати приватний конструктор за замовчуванням. Добре підходить для занять, у яких є лише статичні методи.
intrepidis

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

1
Так, ви можете продовжувати свою думку, і це також буде добре, але я думаю, що навпаки. Класи призначені для інстанцізації об'єктів та зловживання ними, включаючи приватний конструктор за замовчуванням, щоб перешкодити їх використанню, в кращому випадку бентежить. У Java перерахунки насправді є класами, але такі, які ніколи не можна створити інстанцією за дизайном.
intrepidis

0

Як було запропоновано щонайменше двічі, ви можете використовувати descendingIteratorз a Deque, зокрема з a LinkedList. Якщо ви хочете використовувати цикл для кожного циклу (тобто мати ан 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
"Оскільки список масивів не підтримує список у тому ж порядку, що дані були додані до списку" umm, так, так, принаймні, це відбувається так само, як і пов'язаний список. Чи можете ви навести приклад того, як вони цього не роблять?
corsiKa

Так, ArrayList та LinkedList (загальнодоступний контракт із списком) підтримують введення елементів у порядку. встановлений договір не упорядкований або замовлений сортуванням (TreeSet). Зворотна ідея непогана, але пам’ятайте, що вона насправді переробляє список, який може бути повільним.
Білл К

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