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

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
источник
  • Вы должны изменить объявление кэша, по крайней мере, на коллекцию, чтобы на самом деле вы могли называть его добавлением. –  Vincent Robert 16.10.2012 в 22:01
  • Исправить. Не могу вспомнить, почему я объявил кеш как Iterable. Пахнет артефактом copy & paste;) –  Andreas_D 17.10.2012 в 07:28
  • @Andreas_D: Когда я сделал код выше, мой список кеша будет перезаписан новым значением –  Unmesha SreeVeni 01.03.2014 в 07:40
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
источник
  • Спасибо, что приятно знать. –  log0 26.05.2011 в 18:56
10

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

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

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

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

    
ответ дан akappa 24.05.2011 в 15:39
источник
  • ok, так что решение, которое я имел в виду ... (как я сказал в комментарии). В противном случае я не думаю, что могу что-то сделать с подписью. Он навязывается каркасом Hadoop (который я использую). –  log0 24.05.2011 в 16:13
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
источник
  • , но это все равно, поэтому нет никакой разницы в использовании памяти или что-то еще ... это просто приманный способ сохранить две строки кода. Это оправдывает импорт библиотеки? –  akappa 25.05.2011 в 17:08
  • По крайней мере, в моем случае большинство моих приложений уже имеют коллекцию коллекций apache как зависимость по той или иной причине. IMO независимо от того, что пишет ваш собственный доморощенный код, является лучшим (чистым) кодом, но, конечно, вы всегда можете пойти с первым предложением сохранить эти ценности самостоятельно. –  anubhava 25.05.2011 в 17:16
  • Ну, я думаю, что не всегда бывает, что использование некоторых внешних библиотек делает ваш код более читабельным, потому что не у всех есть некоторые сведения о библиотеке, которую вы используете. Для сложных или очень скучных задач, используя библиотеку, это всегда хорошая идея, но для «микро-задач», подобных этому, вам нужно выяснить, что делает «IteratorUtils.toListIterator ()», когда цикл, который хранит значения, немедленно понятный. Не поймите меня неправильно, мне очень нравится Apache Commons, но я думаю, что мы должны использовать (внешние) библиотечные товары со скудством. –  akappa 25.05.2011 в 18:26
  • Это точно моя точка зрения, если это какой-то неясный неслыханный тип библиотеки, мы должны проверить все перед использованием. Но «Apache commons» является одной из наиболее широко используемых библиотек из их пакета услуг. И, как я уже сказал, почти все мои приложения уже используют его, поэтому это не новое дополнение к зависимостям. –  anubhava 25.05.2011 в 18:35
  • @anubhava: Он работает частично для меня. Мне также нужны две итерации. Но когда я обследовал, применив ваш код. В 1-м проходе я могу правильно получить все значения. Но для второго пропуска я только получив первый элемент повторно. Мы можем получить одинаковое значение в обоих проходах –  Unmesha SreeVeni 27.02.2014 в 11:34
Показать остальные комментарии
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
источник
  • +1 Но это не хорошо для большого набора данных, поскольку мы создаем копию того же списка –  Unmesha SreeVeni 17.03.2014 в 12:59
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
источник
  • Можете ли вы объяснить, как это работает? –  Shevliaskovic 18.05.2014 в 08:40
  • @ Шевляскович, кажется само собой разумеющимся: код проходит по списку вперед, затем поворачивается и делает второй проход назад. –  Mark 18.05.2014 в 08:44
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
источник
  • Это похоже на принятый ответ. –  Keppil 05.10.2015 в 12:46