キーと値のペアを格納するために並列配列を広範囲に使用する一連のコードを継承しました。実際にこの方法で行うのは理にかなっていますが、これらの値を反復するループを作成するのは少し厄介です。私は新しいJava foreachコンストラクトが本当に好きですが、これを使用して並列リストを反復する方法はないようです。
通常のfor
ループを使用すると、これを簡単に行うことができます。
for (int i = 0; i < list1.length; ++i) {
doStuff(list1[i]);
doStuff(list2[i]);
}
しかし、私の意見では、反復中にlist2
の境界をチェックしていないため、これは意味的に純粋ではありません。並列リストで使用できるfor-eachに似た巧妙な構文はありますか?
Map
を自分で使用します。しかし、あなたの場合、配列のペアが理にかなっているというWordを理解すると、2つの配列を取り、Iterable
ラッパーを返すユーティリティメソッドはどうでしょうか。
概念的に:
for (Pair<K,V> p : wrap(list1, list2)) {
doStuff(p.getKey());
doStuff(p.getValue());
}
Iterable<Pair<K,V>>
ラッパーは境界チェックを隠します。
拡張されたforループのオラクルの公式ページから:
最後に、複数のコレクションを並行して反復する必要があるループには使用できません。これらの欠点は設計者に知られており、設計者は大多数のケースをカバーするクリーンでシンプルな構成を採用することを意識的に決定しました。
基本的には、通常のforループを使用するのが最善です。
これらの配列のペアを使用してマップをシミュレートする場合、2つの配列を使用してMapインターフェースを実装するクラスを常に作成できます。これにより、ループの多くを抽象化できます。
あなたのコードを見ていないと、このオプションが最善の方法であるかどうかはわかりませんが、検討することはできます。
これは楽しい練習でした。可変数の型付きリストを受け取り、各インデックスの値を反復処理できる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
、それを取り除く方法を誰かが知っている場合は、私に知らせてください:)
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つは配列などです)
これがお役に立てば幸いです。
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);
});
簡単な答え:いいえ
セクシーな反復とJavaバイトコードが必要ですか? Scalaをチェックしてください: Scala for 2つのリストを同時にループする
免責事項:これは確かに「別の言語を使用する」答えです。私を信じて、私はJavaがセクシーな並列反復を持っていることを望みますが、セクシーなコードが欲しいのでJavaで誰も開発を始めませんでした。
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());
}
しかしながら:
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
クラスを記述します(キー/値のペア)」を返信します。これには、並列配列を取るコンストラクターがあり、entrySet
はSet
を実装しているため、Set
を使用して、反復可能なCollection
を返すことができます。 。” –構造explicitを型で作成し、クラスで実装します。
2つの並列コレクションまたは配列を反復処理するには、Iterable<Pair<T, U>>
を反復処理します。これにより、 Zip
(これは@Isaac Truettがwrap
を呼び出します)。ただし、これは慣用的なJavaではありません。ペアの要素は何ですか? Java:Zip
関数の書き方を参照してください。 Javaでこれを書く方法と、なぜそれが推奨されないのかについての広範な議論については、戻り値の型は何ですか? .
これはまさに文体のトレードオフですJavaは、すべてのタイプを正確に把握し、を指定して指定し、それを実装します。