Подтвердить что ты не робот

Итерация дважды по значениям

Я получаю итератор как аргумент, и я хотел бы повторять значения дважды.

public void reduce(Pair<String,String> key, Iterator<IntWritable> values,
                   Context context)

Возможно ли это? Как? Подпись навязывается используемой мной картой (а именно Hadoop).

- изменить -
Наконец, действительная сигнатура метода reduce имеет iterable. Я был введен в заблуждение этой страницей wiki (которая на самом деле является единственным не-устаревшим (но неправильным) примером найденного слова).

4b9b3361

Ответ 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..
    }