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

17

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

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

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

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

задан log0 24.05.2011 в 15:35
источник

10 ответов

9

Мы должны кэшировать значения из итератора, если вы хотите снова итерации. По крайней мере, мы можем объединить первую итерацию и кеширование:

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 фактически сохранял значения. Повторите дважды, либо вам придется сбросить итератор (возможно), либо клонировать его (опять же: невозможно).

Чтобы привести пример для итератора, где клонирование / перезагрузка не имеет никакого смысла:

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 24.05.2011 в 15:55
источник
12

К сожалению, это невозможно без кэширования значений, как в ответе Andreas_D.

Даже используя новый API, где Reducer получает Iterable , а не Iterator , вы не можете повторять итерацию дважды. Очень заманчиво попробовать что-то вроде:

for (IntWritable value : values) {
    // first loop
}

for (IntWritable value : values) {
    // second loop
}

Но это не будет работать. % Co_de%, которое вы получаете от этого Iterator Iterable , является специальным. Значения могут быть не все в памяти; Hadoop может передавать их с диска. Они действительно не поддерживаются iterator() , поэтому нетривиально разрешить несколько итераций.

Вы можете увидеть это сами в коде Collection и Reducer .

Кэширование значений в ReduceContext какого-то рода может быть самым простым ответом, но вы можете легко взорвать кучу, если работаете на больших наборах данных. Если вы можете дать нам больше подробностей о вашей проблеме, мы сможем помочь вам найти решение, не требующее нескольких итераций.

    
ответ дан ajduff574 26.05.2011 в 17:49
источник
10

Повторное использование данного итератора, нет.

Но вы можете сохранить значения в ArrayList при первом итерации через них, а затем, конечно, итерации по построенному ArrayList (или вы можете создать его непосредственно в первую очередь, используя некоторые причудливые методы коллекции, а затем итерации прямо на ArrayList дважды. Это вопрос вкусов).

Во всяком случае, вы уверены, что прохождение Итератора - это хорошая вещь в первую очередь? Итераторы используются для линейного сканирования через коллекцию, поэтому они не выставляют метод «перемотки».

Вы должны передать что-то другое, например Collection<T> или Iterable<T> , как уже было предложено в другом ответе.

    
ответ дан akappa 24.05.2011 в 15:39
источник
6

Итераторы имеют только один проход. Некоторые типы итераторов являются клонируемыми, и вы можете клонировать их перед прохождением, но это не общий случай.

Вместо этого вы должны сделать свою функцию Iterable , если вы можете этого достичь.

    
ответ дан Chris Jester-Young 24.05.2011 в 15:39
источник
2

Если подпись метода не может быть изменена, я бы предложил использовать 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 в моем коде.

    
ответ дан anubhava 24.05.2011 в 19:16
источник
1

Если мы пытаемся дважды итератировать в 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
    
ответ дан Unmesha SreeVeni 01.03.2014 в 10:41
источник
0

Попробуйте следующее:

    ListIterator it = list.listIterator();

    while(it.hasNext()){

        while(it.hasNext()){
            System.out.println("back " + it.next() +" "); 
        }
        while(it.hasPrevious()){
            it.previous();
        }
    }
    
ответ дан Prashob 18.05.2014 в 08:20
источник
0

, если вы хотите изменить значения по мере продвижения, я думаю, что лучше использовать 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 () в том же самом итератор-объекте списка.     

ответ дан yev 23.05.2014 в 07:32
источник
0

вы можете сделать это

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
}
  1. Ссылка Ссылка 2

  2. Ссылка Ссылка 2

ответ дан Meeran0823 28.07.2015 в 06:04
источник
0

После поиска и выполнения многих попыток и ошибок я нашел решение.

  1. Объявить новую коллекцию (например, cache ) (связанный список или Arraylist или любое другое)

  2. Внутри первой итерации назначьте текущий итератор, как показано ниже:

    cache.add(new Text(current.get()))  
    
  3. Итерировать через кеш:

    for (Text count : counts) {
        //counts is iterable object of Type Text
        cache.add(new Text(count.getBytes()));
    }
    for(Text value:cache) {
        // your logic..
    }
    
ответ дан Keval Shah 18.09.2015 в 03:00
источник