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

18

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

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

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

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

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

10 ответов

10

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

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
13

К сожалению, это невозможно без кэширования значений, как в ответе 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 в некотором роде может быть самым простым ответом, но вы можете легко уничтожить кучу, если работаете с большими наборами данных. Если вы сможете подробнее рассказать о вашей проблеме, мы сможем помочь вам найти решение, не включающее несколько итераций.

    
ответ дан ajduff574 26.05.2011 в 17:49
  • Спасибо, что приятно знать. –  log0 26.05.2011 в 18:56
10

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

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

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

Вы должны передать что-то другое, например 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
1

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

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

Попробуйте это:

    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

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

  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