web-dev-qa-db-fra.com

Itérer deux fois sur les valeurs (MapReduce)

Je reçois un itérateur comme argument et je voudrais parcourir deux fois les valeurs.

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

C'est possible ? Comment? La signature est imposée par le framework que j'utilise (à savoir Hadoop).

-- modifier --
Enfin, la vraie signature de la méthode reduce est avec un iterable. J'ai été induit en erreur par cette page wiki (qui est en fait le seul exemple non déprécié (mais faux) de wordcount que j'ai trouvé).

18
log0

Nous devons mettre en cache les valeurs de l'itérateur si vous souhaitez effectuer une nouvelle itération. Au moins, nous pouvons combiner la première itération et la mise en cache:

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);
}

(juste pour ajouter une réponse avec du code, sachant que vous avez mentionné cette solution dans votre propre commentaire;))


pourquoi c'est impossible sans la mise en cache: une Iterator est quelque chose qui implémente une interface et il n'y a pas une seule exigence, que l'objet Iterator stocke réellement des valeurs. Faites une itération deux fois, vous devez soit réinitialiser l’itérateur (non possible), soit le cloner (encore une fois: impossible). 

Pour donner un exemple pour un itérateur où cloner/réinitialiser n’aurait aucun sens:

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"); 
  }
}
10
Andreas_D

Malheureusement, cela n'est pas possible sans mettre en cache les valeurs comme dans la réponse d'Andreas_D.

Même en utilisant la nouvelle API, où la Reducer reçoit une Iterable plutôt qu'une Iterator, vous ne pouvez pas effectuer une nouvelle itération. C'est très tentant d'essayer quelque chose comme:

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

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

Mais cela ne fonctionnera pas réellement. La Iterator que vous recevez de la méthode iterator() de cette Iterable est spéciale. Les valeurs peuvent ne pas être toutes en mémoire; Hadoop les diffuse peut-être à partir du disque. Ils ne sont pas vraiment supportés par une Collection, il n'est donc pas trivial d'autoriser plusieurs itérations.

Vous pouvez le voir vous-même dans les codes Reducer et ReduceContext.

Mettre en cache les valeurs dans une variable Collection peut être la solution la plus simple, mais vous pouvez facilement détruire le tas si vous travaillez sur de grands ensembles de données. Si vous pouvez nous donner plus de détails sur votre problème, nous pourrons peut-être vous aider à trouver une solution ne nécessitant pas plusieurs itérations.

13
ajduff574

Réutiliser l'itérateur donné, non.

Mais vous pouvez sauvegarder les valeurs dans un ArrayList en itérant d’abord entre elles puis sur le ArrayList construit, bien sûr (ou vous pouvez le construire directement en utilisant certaines méthodes de Collection fantaisie puis en itérant directement sur le ArrayList deux fois. C’est une question de goûts).

Quoi qu'il en soit, êtes-vous sûr de passer par un itérateur est une bonne chose en premier lieu? Les itérateurs sont utilisés pour faire un balayage linéaire de la collection, c'est pourquoi ils n'exposent pas de méthode de "rembobinage".

Vous devriez passer quelque chose de différent, comme un Collection<T> ou un Iterable<T>, comme suggéré dans une réponse différente.

10
akappa

Les itérateurs sont un seul parcours. Certains types sont itératifs, et vous pourrez peut-être le cloner avant de traverser, mais ce n'est pas le cas général.

Vous devriez faire en sorte que votre fonction prenne une Iterable à la place, si vous pouvez y parvenir.

6

Si la signature de la méthode ne peut pas être modifiée, je vous suggère d'utiliser Apache Commons IteratorUtils pour convertir Iterator en ListIterator. Considérez cet exemple de méthode pour itérer deux fois des valeurs:

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());
}

En utilisant le code ci-dessus, j'ai pu parcourir la liste de valeurs without en enregistrant une copie des éléments de la liste dans mon code.

2
anubhava

Si nous essayons d'itérer deux fois dans Réducteur comme ci-dessous

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());

Nous ne sortirons que

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

Afin de le faire correctement, nous devrions boucler comme ceci:

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));
  }

Sortie

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
1
Unmesha SreeVeni

vous pouvez le faire

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. Lien de référence 2

  2. Lien de référence 2

1
Meeran0823

si vous voulez changer les valeurs au fur et à mesure, j'imagine qu'il vaut mieux utiliser listIterator que la méthode 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());
}

Au lieu d'appeler .previous (), je viens d'obtenir une autre instance de .listIterator () sur le même objet itérateur de liste. 

0
yev

Après avoir cherché et fait beaucoup d'essais et d'erreurs, j'ai trouvé une solution.

  1. Déclarer une nouvelle collection (disons cache) (liste chaînée ou liste de tâches ou autre) 

  2. Dans la première itération, assignez l'itérateur actuel comme ci-dessous:

    cache.add(new Text(current.get()))  
    
  3. Itérer dans le cache:

    for (Text count : counts) {
        //counts is iterable object of Type Text
        cache.add(new Text(count.getBytes()));
    }
    for(Text value:cache) {
        // your logic..
    }
    
0
Keval Shah

Essaye ça:

    ListIterator it = list.listIterator();

    while(it.hasNext()){

        while(it.hasNext()){
            System.out.println("back " + it.next() +" "); 
        }
        while(it.hasPrevious()){
            it.previous();
        }
    }
0
Prashob