Итерация дважды по значениям
Я получаю итератор как аргумент, и я хотел бы повторять значения дважды.
public void reduce(Pair<String,String> key, Iterator<IntWritable> values,
Context context)
Возможно ли это? Как?
Подпись навязывается используемой мной картой (а именно Hadoop).
- изменить -
Наконец, действительная сигнатура метода reduce
имеет iterable
. Я был введен в заблуждение этой страницей wiki (которая на самом деле является единственным не-устаревшим (но неправильным) примером найденного слова).
Ответы
Ответ 1
Мы должны кэшировать значения из итератора, если вы хотите снова итерации. По крайней мере, мы можем объединить первую итерацию и кеширование:
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);
}
(просто чтобы добавить ответ с кодом, зная, что вы упомянули это решение в своем собственном комментарии;))
почему это невозможно без кеширования: Iterator
- это то, что реализует интерфейс, и нет ни одного требования, чтобы объект Iterator
фактически хранил значения. Повторите дважды, либо вам нужно reset итератор (невозможно), либо клонировать его (снова: невозможно).
Чтобы привести пример для итератора, где клонирование/перезагрузка не имеет никакого смысла:
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");
}
}
Ответ 2
К сожалению, это невозможно без кэширования значений, как в ответе Andreas_D.
Даже используя новый API, где Reducer
получает Iterable
, а не Iterator
, вы не можете повторять итерацию дважды. Очень заманчиво попробовать что-то вроде:
for (IntWritable value : values) {
// first loop
}
for (IntWritable value : values) {
// second loop
}
Но это не будет работать. Iterator
, который вы получаете от этого метода Iterable
iterator()
, является специальным. Значения могут быть не все в памяти; Hadoop может передавать их с диска. На них не поддерживается Collection
, поэтому нетривиально разрешить несколько итераций.
Вы можете увидеть это сами в Reducer
и ReduceContext
.
Кэширование значений в некотором виде Collection
может быть самым легким ответом, но вы можете легко взорвать кучу, если работаете на больших наборах данных. Если вы можете дать нам больше подробностей о вашей проблеме, мы сможем помочь вам найти решение, не требующее нескольких итераций.
Ответ 3
Повторное использование данного итератора, нет.
Но вы можете сохранить значения в ArrayList при первом итерации через них, а затем, конечно, итерации по построенному ArrayList (или вы можете создать его непосредственно в первую очередь, используя некоторые причудливые методы коллекции, а затем итерации прямо на ArrayList дважды. Это вопрос вкусов).
Во всяком случае, вы уверены, что прохождение Итератора - это хорошая вещь в первую очередь?
Итераторы используются для линейного сканирования через коллекцию, поэтому они не выставляют метод "перемотки".
Вы должны передать что-то другое, например Collection<T>
или Iterable<T>
, как уже было предложено в другом ответе.
Ответ 4
Итераторы имеют только один проход. Некоторые типы итераторов являются клонируемыми, и вы можете клонировать их перед обходом, но это не общий случай.
Вместо этого вы должны сделать свою функцию Iterable
, если вы можете этого достичь.
Ответ 5
Если подпись метода не может быть изменена, я бы предложил использовать Apache Commons IteratorUtils для преобразования Iterator в ListIterator. Рассмотрим этот пример для повторного итерации значений:
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());
}
Используя такой код, я смог выполнить итерацию по списку значений без, сохраняя копию элементов List в моем коде.
Ответ 6
Если мы пытаемся дважды итератировать в Reducer, как показано ниже
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
Ответ 7
Попробуйте следующее:
ListIterator it = list.listIterator();
while(it.hasNext()){
while(it.hasNext()){
System.out.println("back " + it.next() +" ");
}
while(it.hasPrevious()){
it.previous();
}
}
Ответ 8
если вы хотите изменить значения по мере продвижения, я думаю, что лучше использовать 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() в том же самом итератор-объекте списка.
Ответ 9
вы можете сделать это
MarkableIterator<Text> mitr = new MarkableIterator<Text>(values.iterator());
mitr.mark();
while (mitr.hasNext())
{
//do your work
}
mitr.reset();
while(mitr.hasNext())
{
//again do your work
}
Ответ 10
После поиска и выполнения многих попыток и ошибок я нашел решение.
-
Объявить новую коллекцию (скажем 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..
}