引数としてイテレータを受け取り、値を2回繰り返したいと思います。
public void reduce(Pair<String,String> key, Iterator<IntWritable> values,
Context context)
出来ますか ?どうやって ?署名は、私が使用しているフレームワーク(つまり、Hadoop)によって課されます。
-編集-
最後に、reduce
メソッドの実際の署名はiterable
を使用します。私はこれに惑わされました wikiページ (これは実際に私が見つけた唯一の非推奨ではない(しかし間違った)ワードカウントの例です)。
もう一度反復する場合は、イテレータから値をキャッシュする必要があります。少なくとも、最初の反復とキャッシングを組み合わせることができます。
Iterator<IntWritable> it = getIterator();
List<IntWritable> cache = new ArrayList<IntWritable>();
// first loop and caching
while (it.hasNext()) {
IntWritable value = it.next();
doSomethingWithValue();
cache.add(value);
}
// second loop
for(IntWritable value:cache) {
doSomethingElseThatCantBeDoneInFirstLoop(value);
}
(あなたがあなた自身のコメントでこの解決策に言及したことを知って、コードで答えを追加するだけです;))
whyキャッシュなしでは不可能です:Iterator
はインターフェイスを実装するものであり、Iterator
オブジェクトが実際に値を格納するという単一の要件はありません。イテレータをリセットする(不可能)か、クローンを作成する(繰り返し:不可能)必要がある場合は、2回繰り返します。
クローン作成/リセットが意味をなさないイテレータの例を示すには:
public class Randoms implements Iterator<Double> {
private int counter = 10;
@Override
public boolean hasNext() {
return counter > 0;
}
@Override
public boolean next() {
count--;
return Math.random();
}
@Override
public boolean remove() {
throw new UnsupportedOperationException("delete not supported");
}
}
残念ながら、Andreas_Dの回答のように、値をキャッシュしないとこれは不可能です。
Reducer
がIterable
ではなくIterator
を受け取る新しいAPIを使用しても、2回繰り返すことはできません。次のようなことを試してみるのは非常に魅力的です。
_for (IntWritable value : values) {
// first loop
}
for (IntWritable value : values) {
// second loop
}
_
しかし、これは実際には機能しません。そのIterator
のiterator()
メソッドから受け取るIterable
は特別です。値がすべてメモリにあるとは限りません。 Hadoopがディスクからそれらをストリーミングしている可能性があります。それらは実際にはCollection
に支えられていないため、複数の反復を許可することは簡単ではありません。
これは、Reducer
およびReduceContext
コードで確認できます。
ある種のCollection
に値をキャッシュするのが最も簡単な答えかもしれませんが、大規模なデータセットを操作している場合は、ヒープを簡単に破壊できます。問題の詳細を教えていただければ、複数回の反復を伴わない解決策を見つけるお手伝いができる場合があります。
指定されたイテレータを再利用します。
ただし、もちろん、最初に値を反復処理してから構築されたArrayListを反復処理するときに、ArrayListに値を保存できます(または、いくつかの凝ったコレクションメソッドを使用して最初に直接構築し、次に直接反復処理することもできます) ArrayListを2回。好みの問題です)。
とにかく、そもそもイテレータを渡すのは良いことだと思いますか?イテレータは、コレクション全体を線形スキャンするために使用されます。これが、「巻き戻し」メソッドを公開しない理由です。
別の回答ですでに提案されているように、Collection<T>
やIterable<T>
などの別のものを渡す必要があります。
イテレータは1回の走査のみです。 一部イテレータタイプは複製可能であり、トラバースする前に複製できる場合がありますが、これは一般的なケースではありません。
実現できるのであれば、代わりに関数にIterable
を使用させる必要があります。
メソッドのシグネチャを変更できない場合は、 Apache Commons IteratorUtils を使用してIteratorをListIteratorに変換することをお勧めします。値を2回反復する次のメソッド例を検討してください。
void iterateTwice(Iterator<String> it) {
ListIterator<?> lit = IteratorUtils.toListIterator(it);
System.out.println("Using ListIterator 1st pass");
while(lit.hasNext())
System.out.println(lit.next());
// move the list iterator back to start
while(lit.hasPrevious())
lit.previous();
System.out.println("Using ListIterator 2nd pass");
while(lit.hasNext())
System.out.println(lit.next());
}
上記のようなコードを使用して、コードにリスト要素のコピーを保存せずに値のリストを反復処理することができました。
以下のようにReducerで2回反復しようとしている場合
ListIterator<DoubleWritable> lit = IteratorUtils.toListIterator(it);
System.out.println("Using ListIterator 1st pass");
while(lit.hasNext())
System.out.println(lit.next());
// move the list iterator back to start
while(lit.hasPrevious())
lit.previous();
System.out.println("Using ListIterator 2nd pass");
while(lit.hasNext())
System.out.println(lit.next());
としてのみ出力します
Using ListIterator 1st pass
5.3
4.9
5.3
4.6
4.6
Using ListIterator 2nd pass
5.3
5.3
5.3
5.3
5.3
正しい方法で取得するには、次のようにループする必要があります。
ArrayList<DoubleWritable> cache = new ArrayList<DoubleWritable>();
for (DoubleWritable aNum : values) {
System.out.println("first iteration: " + aNum);
DoubleWritable writable = new DoubleWritable();
writable.set(aNum.get());
cache.add(writable);
}
int size = cache.size();
for (int i = 0; i < size; ++i) {
System.out.println("second iteration: " + cache.get(i));
}
出力
first iteration: 5.3
first iteration: 4.9
first iteration: 5.3
first iteration: 4.6
first iteration: 4.6
second iteration: 5.3
second iteration: 4.9
second iteration: 5.3
second iteration: 4.6
second iteration: 4.6
値を変更したい場合は、listIteratorを使用してからset()メソッドを使用する方が良いと思います。
ListIterator lit = list.listIterator();
while(lit.hasNext()){
String elem = (String) lit.next();
System.out.println(elem);
lit.set(elem+" modified");
}
lit = null;
lit = list.listIterator();
while(lit.hasNext()){
System.out.println(lit.next());
}
.previous()を呼び出す代わりに、同じリストイテレータオブジェクトで.listIterator()の別のインスタンスを取得します。
たくさんの試行錯誤を検索して実行した後、私は解決策を見つけました。
新しいコレクションを宣言します(たとえば、cache
)(リンクリストまたはArraylistまたはその他)
最初の反復内で、以下の例のように現在のイテレータを割り当てます。
cache.add(new Text(current.get()))
キャッシュを反復処理します。
for (Text count : counts) {
//counts is iterable object of Type Text
cache.add(new Text(count.getBytes()));
}
for(Text value:cache) {
// your logic..
}
これを試して:
ListIterator it = list.listIterator();
while(it.hasNext()){
while(it.hasNext()){
System.out.println("back " + it.next() +" ");
}
while(it.hasPrevious()){
it.previous();
}
}