web-dev-qa-db-ja.com

Java foreachを使用して並列配列を反復する気の利いた方法

キーと値のペアを格納するために並列配列を広範囲に使用する一連のコードを継承しました。実際にこの方法で行うのは理にかなっていますが、これらの値を反復するループを作成するのは少し厄介です。私は新しいJava foreachコンストラクトが本当に好きですが、これを使用して並列リストを反復する方法はないようです。

通常のforループを使用すると、これを簡単に行うことができます。

for (int i = 0; i < list1.length; ++i) {
    doStuff(list1[i]);
    doStuff(list2[i]);
}

しかし、私の意見では、反復中にlist2の境界をチェックしていないため、これは意味的に純粋ではありません。並列リストで使用できるfor-eachに似た巧妙な構文はありますか?

32
Travis Webb

Mapを自分で使用します。しかし、あなたの場合、配列のペアが理にかなっているというWordを理解すると、2つの配列を取り、Iterableラッパーを返すユーティリティメソッドはどうでしょうか。

概念的に:

for (Pair<K,V> p : wrap(list1, list2)) {
    doStuff(p.getKey());
    doStuff(p.getValue());
}

Iterable<Pair<K,V>>ラッパーは境界チェックを隠します。

22
Isaac Truett

拡張されたforループのオラクルの公式ページから:

最後に、複数のコレクションを並行して反復する必要があるループには使用できません。これらの欠点は設計者に知られており、設計者は大多数のケースをカバーするクリーンでシンプルな構成を採用することを意識的に決定しました。

基本的には、通常のforループを使用するのが最善です。

これらの配列のペアを使用してマップをシミュレートする場合、2つの配列を使用してMapインターフェースを実装するクラスを常に作成できます。これにより、ループの多くを抽象化できます。

あなたのコードを見ていないと、このオプションが最善の方法であるかどうかはわかりませんが、検討することはできます。

11
Tikhon Jelvis

これは楽しい練習でした。可変数の型付きリストを受け取り、各インデックスの値を反復処理できるParallelListというオブジェクトを作成しました(値のリストとして返されます)。

public class ParallelList<T> implements Iterable<List<T>> {

    private final List<List<T>> lists;

    public ParallelList(List<T>... lists) {
        this.lists = new ArrayList<List<T>>(lists.length);
        this.lists.addAll(Arrays.asList(lists));
    }

    public Iterator<List<T>> iterator() {
        return new Iterator<List<T>>() {
            private int loc = 0;

            public boolean hasNext() {
                boolean hasNext = false;
                for (List<T> list : lists) {
                    hasNext |= (loc < list.size());
                }
                return hasNext;
            }

            public List<T> next() {
                List<T> vals = new ArrayList<T>(lists.size());
                for (int i=0; i<lists.size(); i++) {
                    vals.add(loc < lists.get(i).size() ? lists.get(i).get(loc) : null);
                }
                loc++;
                return vals;
            }

            public void remove() {
                for (List<T> list : lists) {
                    if (loc < list.size()) {
                        list.remove(loc);
                    }
                }
            }
        };
    }
}

使用例:

List<Integer> list1 = Arrays.asList(new Integer[] {1, 2, 3, 4, 5});
List<Integer> list2 = Arrays.asList(new Integer[] {6, 7, 8});
ParallelList<Integer> list = new ParallelList<Integer>(list1, list2);
for (List<Integer> ints : list) {
    System.out.println(String.format("%s, %s", ints.get(0), ints.get(1)));
}

どちらが出力されます:

1, 6
2, 7
3, 8
4, null
5, null

このオブジェクトは可変長のリストをサポートしますが、より厳密になるように変更できることは明らかです。

残念ながら、ParallelListコンストラクタに関するコンパイラの警告を1つも取り除くことができませんでした:A generic array of List<Integer> is created for varargs parameters、それを取り除く方法を誰かが知っている場合は、私に知らせてください:)

9
Sean Adkinson

Forループで2番目の制約を使用できます。

    for (int i = 0; i < list1.length && i < list2.length; ++i) 
    {
      doStuff(list1[i]);
      doStuff(list2[i]);
    }//for

コレクションをトラバースするための私の好ましい方法の1つはfor-eachループですが、Oracleチュートリアルで言及しているように、並列コレクションを処理する場合は for-each ではなくイテレータを使用します。

以下は、同様の post での Martin v。Löwis による回答でした。

it1 = list1.iterator();
it2 = list2.iterator();
while(it1.hasNext() && it2.hasNext()) 
{
   value1 = it1.next();
   value2 = it2.next();

   doStuff(value1);
   doStuff(value2);
}//while

イテレータの利点は、ジェネリックであるため、使用されているコレクションがわからない場合はイテレータを使用します。そうでない場合は、コレクションがわかっている場合は、長さ/サイズ関数を知っているため、通常のforループになります。ここで追加の制約を使用できます。 (興味深い投稿は、使用されるコレクションが異なる場合があるため、この投稿では非常に複数であることに注意してください。たとえば、1つはリストで、もう1つは配列などです)

これがお役に立てば幸いです。

6
Alexei Blue

Java 8の場合、これらを使用してセクシーな方法でループします。

//parallel loop
public static <A, B> void loop(Collection<A> a, Collection<B> b, IntPredicate intPredicate, BiConsumer<A, B> biConsumer) {
    Iterator<A> ait = a.iterator();
    Iterator<B> bit = b.iterator();
    if (ait.hasNext() && bit.hasNext()) {
        for (int i = 0; intPredicate.test(i); i++) {
            if (!ait.hasNext()) {
                ait = a.iterator();
            }
            if (!bit.hasNext()) {
                bit = b.iterator();
            }
            biConsumer.accept(ait.next(), bit.next());
        }
    }
}

//nest loop
public static <A, B> void loopNest(Collection<A> a, Collection<B> b, BiConsumer<A, B> biConsumer) {
    for (A ai : a) {
        for (B bi : b) {
            biConsumer.accept(ai, bi);
        }
    }
}

これらの2つのリストを使用したいくつかの例:

List<Integer> a = Arrays.asList(1, 2, 3);
List<String> b = Arrays.asList("a", "b", "c", "d");

aおよびbの最小サイズ内のループ

loop(a, b, i -> i < Math.min(a.size(), b.size()), (x, y) -> {
    System.out.println(x +  " -> " + y);
});

出力:

1 -> a
2 -> b
3 -> c

aおよびbの最大サイズ内でループします(短いリストの要素は循環します):

loop(a, b, i -> i < Math.max(a.size(), b.size()), (x, y) -> {
    System.out.println(x +  " -> " + y);
});

出力:

1 -> a
2 -> b
3 -> c
1 -> d

ループn回((nがリストのサイズより大きい場合、要素は循環します)):

loop(a, b, i -> i < 5, (x, y) -> {
    System.out.println(x +  " -> " + y);
});

出力:

1 -> a
2 -> b
3 -> c
1 -> d
2 -> a

永遠にループ:

loop(a, b, i -> true, (x, y) -> {
    System.out.println(x +  " -> " + y);
});

あなたの状況に適用してください:

loop(list1, list2, i -> i < Math.min(a.size(), b.size()), (e1, e2) -> {
    doStuff(e1);
    doStuff(e2);
});
1
yelliver

簡単な答え:いいえ

セクシーな反復とJavaバイトコードが必要ですか? Scalaをチェックしてください: Scala for 2つのリストを同時にループする

免責事項:これは確かに「別の言語を使用する」答えです。私を信じて、私はJavaがセクシーな並列反復を持っていることを望みますが、セクシーなコードが欲しいのでJavaで誰も開発を始めませんでした。

1
Joseph Lust

ArrayIterator を使用すると、インデックス作成を回避できますが、個別のクラスまたは少なくとも関数を作成せずにfor-eachループを使用することはできません。 @Alexei Blueの発言として、公式の推奨事項( コレクションインターフェイス で)は次のとおりです。「必要な場合は、for-each構成の代わりにIteratorを使用してください。 …複数のコレクションを並行して反復します。」:

import static com.google.common.base.Preconditions.checkArgument;
import org.Apache.commons.collections.iterators.ArrayIterator;

// …

  checkArgument(array1.length == array2.length);
  Iterator it1 = ArrayIterator(array1);
  Iterator it2 = ArrayIterator(array2);
  while (it1.hasNext()) {
      doStuff(it1.next());
      doOtherStuff(it2.next());
  }

しかしながら:

  • 配列のインデックス付けは自然です–配列は定義によって定義インデックス付けされたものであり、元のコードのようにforループの数値は完全に自然でより多くです直接。
  • @Isaac Truettが言うように、キーと値のペアは自然にMapを形成します。したがって、最もクリーンなのは、すべての並列配列のマップを作成することです(したがって、このループは、マップを作成するファクトリ関数にのみ存在します)。これを繰り返し処理したいだけの場合、これは非効率的です。 (重複をサポートする必要がある場合は、 Multimap を使用してください。)
  • これらがたくさんある場合は、(部分的に)ParallelArrayMap<>(つまり、並列配列に基づくマップ)を実装するか、またはParallelArrayHashMap<>(必要に応じてHashMapを追加する)を実装できます。キーによる効率的な検索)、そしてそれを使用して、元の順序での反復を可能にします。これはおそらくやり過ぎですが、セクシーな答えを可能にします。

あれは:

Map<T, U> map = new ParallelArrayMap<>(array1, array2);
for (Map.Entry<T, U> entry : map.entrySet()) {
  doStuff(entry.getKey());
  doOtherStuff(entry.getValue());
}

哲学的に、Javaスタイルは明示的、名前付き型で、クラスによって実装されます。したがって、「[I持つ]並列配列[それ]はキー/値のペアを格納します。」、Javaは、「ParallelArrayMapを実装するMapクラスを記述します(キー/値のペア)」を返信します。これには、並列配列を取るコンストラクターがあり、entrySetSetを実装しているため、Setを使用して、反復可能なCollectionを返すことができます。 。” –構造explicitを型で作成し、クラスで実装します。

2つの並列コレクションまたは配列を反復処理するには、Iterable<Pair<T, U>>を反復処理します。これにより、 Zip (これは@Isaac Truettがwrapを呼び出します)。ただし、これは慣用的なJavaではありません。ペアの要素は何ですか? Java:Zip関数の書き方を参照してください。 Javaでこれを書く方法と、なぜそれが推奨されないのかについての広範な議論については、戻り値の型は何ですか? .

これはまさに文体のトレードオフですJavaは、すべてのタイプを正確に把握し、を指定して指定し、それを実装します。

0
Nils von Barth