Ітерація через список у зворотному порядку в 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.