Javaで逆の順序でリストを反復する


251

ジェネリックを利用するためにコードの一部を移行しています。これを行うための1つの引数は、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コメント)
Allain Lalonde 2010年

1
@ x4u:Iteratorはフェイルファストであり、反復中に要素を簡単に削除することもできますが、それほど多くはありません。
アダムスキー

2
このクラスは、ユーザーが同じIterableに対して2回目の反復を実行したり、反復可能オブジェクトが作成されたときと反復されたときにリストが変わったりするため、壊れています。私はあなたの目的のために、あなたがそれをしないことを確認するだろうと確信していますが、コードを修正することはそれほど難しくありません。:またはちょうどグアバ(Apache 2.0のライセンス)からコードを盗む code.google.com/p/guava-libraries/source/browse/trunk/src/com/...
ケビンBourrillion

1
十分に公平ですが、グアバのものでも、私が正しく読んでいれば同じことが起こりやすくなります。リバースの結果のコピーをユーザーが保持していた場合、同じ問題が発生します。
Allain Lalonde、2010年

回答:


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。
Allain Lalonde、2010年

26
インデックス引数なしでlistIterator()を呼び出すと、リストの先頭にIteratorが与えられるため、最初の呼び出しでhasPrevious()はfalseを返します。
アダムスキー

2
あなたはlistIterator電話でインデックスが欲しいと思います。
トムホーティン-タックライン

1
を逆にIterator使用してを書くListIteratorこともできますが、1つのループでは価値がない場合があります。
トム・ホーティン-10

1
これは1つのループではないので、これを使用してラップしました。pastebin.ca/1759041なので、今やることができますfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde 2010年

35

グアバオファーLists#reverse(List)ImmutableList#reverse()。グアバのほとんどの場合と同様に、引数がの場合、前者は後者に委任するImmutableListため、すべての場合に前者を使用できます。これらはリストの新しいコピーを作成するのではなく、単にリストの「逆ビュー」を作成します。

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

23

forループ構文を使用することは不可能だと思います。私が提案できる唯一のことは、次のようなことをすることです:

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

...しかし、効率が低下することを考えると、これが「よりクリーン」であるとは言えません。


26
また、トラバースするリストの場所も変わります。これは非常に大きな副作用です。(これをメソッドにラップするとします。呼び出されるたびに、リストを逆方向に
たどり

これは最もクリーンなソリューションです。
jfajunior

14

オプション1:Collections#reverse()でリストを逆にしてからforeachを使用することを考えましたか?

もちろん、リストが正しく順序付けられるようにコードをリファクタリングして、余分なスペース/時間を使用してリストを逆にする必要がないようにすることもできます。


編集:

オプション2:または、ArrayListの代わりにDequeを使用できますか?それはあなたが前方と後方に反復することを可能にします


編集:

オプション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
考えたことがありますが、それを元に戻すには莫大な費用がかかります。また、この種の反復は約半分の時間で済みます。それをひっくり返すと、問題はもう半分に移ります。
Allain Lalonde

3
+ Dequeの場合。典型的な実装は持っていdescendingIterator()ます。
trashgod

これはリンクされたリストではひどいパフォーマンスを示しますが、OPのバリアントの方が優れています。
masterxilo 2014年

1
for each式を使用することは、私の考えでは最も慣用的な解決策です。リストがIterableを逆方向に反復する方法で実装している場合、これが可能であることを理解するのは良いことです。このアプローチを使用して、Apache Commons CollectionのReverseListIteratorクラスを使用します。
David Groomes 14

11

LinkedList一般的なインターフェースの代わりに具象クラスを使用できますList。次にdescendingIterator、逆方向で反復するためのがあります。

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

何もそこにある理由をわからないdescendingIteratorArrayList...


9

これは古い質問ですが、java8に適した答えが欠けています。ストリーミング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(size); Stream.generate(it :: previous).limit(size).forEach(System.out :: println);
benez 2016年

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

パブリックコンストラクターの代わりに静的メソッドとして公開すると(ほとんどの場合)、クライアントがtypeパラメーターを指定する必要がなくなりますが、私には正しいように見えます。これは、グアバはそれをしない方法..です
ケビンBourrillion

2
これは最良の実装ReverseIteratorですが、必要なコンストラクタが欠落しているため、コードではのList代わりに使用する必要がありますArrayList
アンディ

5

そのパフォーマンスが本当の問題ではありませんので、リストはかなり小さい場合は、1が使用できる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


なぜこれが反対票を投じているのかわからない。私はこれを行うリストのラッパーを作成するだけかもしれない。
Allain Lalonde、2010年

これは、Collections.reverse()IMHOを呼び出すよりもきれいです。
アダムスキー2010年

@Allain:同意しました。あなたがlistIterator(list.size())の呼び出しを「クリーンでない」と見なす理由はわかりませんが、反対票を投じます。ラップしても、同じメソッドをどこかに呼び出さなければなりません。
アダムスキー2010年

清潔さのために、パフォーマンスヒットを受け入れたくない、とは思わない。
Allain Lalonde

18
私はこれを完全な答えとは考えていませんので、反対票を投じました。
Maarten Bodewes 14

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) {
  ...
}


2

googleコレクションのメソッドも見つかりました。


Googleコレクションのどのバージョンですか?あなたのリンクはもう存在しません。
AaA

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
アンディ14

1
クラスではなく列挙型を使用するのはなぜですか?
Aubin 2014

1
プライベートなデフォルトコンストラクタを記述する必要なしに、「In」クラスをインスタンス化できなくなります。静的メソッドのみを持つクラスに適しています。
intrepidis 2014

プライベートデフォルトコンストラクターの作成を回避するためだけに列挙型を乱用することは、よくても混乱を招くと言えます。クラスの列挙型とクラスの列挙型を使用してどうですか?クラスを列挙型にすることで、name()メソッド、メソッド、ordinal()メソッドなどを暗黙的に取得static valueOf()します。
JHH 2018年

1
はい、あなたはあなたの意見を続けることができ、それもうまくいきますが、私は反対だと思います。クラスは、オブジェクトをインスタンス化し、インスタンス化を禁止するプライベートデフォルトコンストラクターを含めることでオブジェクトを悪用するためのものであり、混乱を招くだけです。Javaでは、列挙型は実際にはクラスですが、設計によってインスタンス化することはできません。
intrepidis

0

あなたが使用することができ、二倍以上で示唆されているdescendingIteratorDequeし、特にLinkedList。for-eachループを使用する場合(つまり、がある場合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にdescendingIteratorがないのかわからない...」

配列リストは、データがリストに追加されたのと同じ順序でリストを保持しないため。したがって、決して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 2013年

はい、ArrayListとLinkedList(一般にListコントラクト)は、項目を順番に挿入し続けます。Setコントラクトは、順序付けされていないか、並べ替え順(TreeSet)です。逆の考えは悪くありませんが、遅くなる可能性があるリストを実際に並べ替えることを覚えておいてください。
ビルK

2
ArrayListsがアイテムを挿入順に保持しないと誤って述べているため、私はこの回答に反対しました。@ bill-kのメモとして、すべてのリストは定義により順序付けられたコレクションです。代わりにLinkedListを使用するという提案にはメリットがあります。これはdescendingIterator()メソッドを備えているためですが、パフォーマンスがメモリと抽象化よりも大きな懸念事項である場合に限られます。
Michael Scheper 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.