classroom - java foreach index



Façon astucieuse d'itérer sur des tableaux parallèles en Java en utilisant foreach (6)

J'ai hérité d'un tas de code qui utilise beaucoup de tableaux parallèles pour stocker des paires clé / valeur. En fait, il est logique de le faire de cette façon, mais c'est un peu gênant d'écrire des boucles qui parcourent ces valeurs. J'aime vraiment la nouvelle construction de Java foreach, mais il ne semble pas qu'il y ait un moyen d'itérer sur des listes parallèles en utilisant ceci.

Avec une boucle normale, je peux le faire facilement:

for (int i = 0; i < list1.length; ++i) {
    doStuff(list1[i]);
    doStuff(list2[i]);
}

Mais à mon avis, ce n'est pas sémantiquement pur, puisque nous ne vérifions pas les limites de list2 pendant l'itération. Y a-t-il une syntaxe astucieuse similaire à celle que je peux utiliser avec des listes parallèles?

https://ffff65535.com


De la page officielle d'Oracle sur la boucle for enhanced:

Enfin, il n'est pas utilisable pour les boucles qui doivent parcourir plusieurs collections en parallèle. Ces défauts étaient connus des concepteurs, qui ont délibérément choisi d'adopter une construction propre et simple qui couvrirait la grande majorité des cas.

Fondamentalement, il vaut mieux utiliser la boucle normale.

Si vous utilisez ces paires de tableaux pour simuler une carte, vous pouvez toujours écrire une classe qui implémente l'interface de carte avec les deux tableaux; Cela pourrait vous permettre de faire abstraction d'une grande partie de la boucle.

Sans regarder votre code, je ne peux pas vous dire si cette option est la meilleure solution, mais c'est quelque chose que vous pourriez envisager.


J'utiliserais une Map moi-même. Mais en vous prenant au mot qu'une paire de tableaux a du sens dans votre cas, que diriez-vous d'une méthode utilitaire qui prend vos deux tableaux et renvoie un wrapper Iterable ?

Conceptuellement:

for (Pair<K,V> p : wrap(list1, list2)) {
    doStuff(p.getKey());
    doStuff(p.getValue());
}

Le wrapper Iterable<Pair<K,V>> masquerait la vérification des limites.


C'était un exercice amusant. J'ai créé un objet appelé ParallelList qui prend un nombre variable de listes typées et peut parcourir les valeurs de chaque index (renvoyé sous la forme d'une liste de valeurs):

public class ParallelList<T> implements Iterable<List<T>> {

    private final List<List<T>> lists;

    public ParallelList(List<T>... lists) {
        this.lists = new ArrayList<List<T>>(lists.length);
        this.lists.addAll(Arrays.asList(lists));
    }

    public Iterator<List<T>> iterator() {
        return new Iterator<List<T>>() {
            private int loc = 0;

            public boolean hasNext() {
                boolean hasNext = false;
                for (List<T> list : lists) {
                    hasNext |= (loc < list.size());
                }
                return hasNext;
            }

            public List<T> next() {
                List<T> vals = new ArrayList<T>(lists.size());
                for (int i=0; i<lists.size(); i++) {
                    vals.add(loc < lists.get(i).size() ? lists.get(i).get(loc) : null);
                }
                loc++;
                return vals;
            }

            public void remove() {
                for (List<T> list : lists) {
                    if (loc < list.size()) {
                        list.remove(loc);
                    }
                }
            }
        };
    }
}

Exemple d'utilisation:

List<Integer> list1 = Arrays.asList(new Integer[] {1, 2, 3, 4, 5});
List<Integer> list2 = Arrays.asList(new Integer[] {6, 7, 8});
ParallelList<Integer> list = new ParallelList<Integer>(list1, list2);
for (List<Integer> ints : list) {
    System.out.println(String.format("%s, %s", ints.get(0), ints.get(1)));
}

Ce qui imprimerait:

1, 6
2, 7
3, 8
4, null
5, null

Cet objet supporte des listes de longueurs variables, mais il est clair qu'il pourrait être modifié pour être plus strict.

Malheureusement, je ne pouvais pas me débarrasser d'un avertissement du compilateur sur le constructeur ParallelList: A generic array of List<Integer> is created for varargs parameters , donc si quelqu'un sait comment se débarrasser de cela, faites le moi savoir :)


Avec Java 8, je les utilise pour boucler de façon sexy:

//parallel loop
public static <A, B> void loop(Collection<A> a, Collection<B> b, IntPredicate intPredicate, BiConsumer<A, B> biConsumer) {
    Iterator<A> ait = a.iterator();
    Iterator<B> bit = b.iterator();
    if (ait.hasNext() && bit.hasNext()) {
        for (int i = 0; intPredicate.test(i); i++) {
            if (!ait.hasNext()) {
                ait = a.iterator();
            }
            if (!bit.hasNext()) {
                bit = b.iterator();
            }
            biConsumer.accept(ait.next(), bit.next());
        }
    }
}

//nest loop
public static <A, B> void loopNest(Collection<A> a, Collection<B> b, BiConsumer<A, B> biConsumer) {
    for (A ai : a) {
        for (B bi : b) {
            biConsumer.accept(ai, bi);
        }
    }
}

Quelques exemples, avec ces 2 listes:

List<Integer> a = Arrays.asList(1, 2, 3);
List<String> b = Arrays.asList("a", "b", "c", "d");

Boucle dans la taille minimale de a et b :

loop(a, b, i -> i < Math.min(a.size(), b.size()), (x, y) -> {
    System.out.println(x +  " -> " + y);
});

Sortie:

1 -> a
2 -> b
3 -> c

Boucle dans la taille maximale de a et b (les éléments de la liste plus courte seront cyclés):

loop(a, b, i -> i < Math.max(a.size(), b.size()), (x, y) -> {
    System.out.println(x +  " -> " + y);
});

Sortie:

1 -> a
2 -> b
3 -> c
1 -> d

Boucle n fois (les éléments seront cyclés si n est plus grand que la taille des listes):

loop(a, b, i -> i < 5, (x, y) -> {
    System.out.println(x +  " -> " + y);
});

Sortie:

1 -> a
2 -> b
3 -> c
1 -> d
2 -> a

Boucle pour toujours:

loop(a, b, i -> true, (x, y) -> {
    System.out.println(x +  " -> " + y);
});

Appliquez à votre situation:

loop(list1, list2, i -> i < Math.min(a.size(), b.size()), (e1, e2) -> {
    doStuff(e1);
    doStuff(e2);
});

Vous pouvez utiliser une deuxième contrainte dans votre boucle for:

    for (int i = 0; i < list1.length && i < list2.length; ++i) 
    {
      doStuff(list1[i]);
      doStuff(list2[i]);
    }//for

L'une de mes méthodes préférées pour parcourir les collections est la boucle for-each, mais comme le mentionne le tutoriel oracle, lorsqu'il s'agit de collections parallèles pour utiliser l' itérateur plutôt que le for-each .

Voici une réponse de Martin v. Löwis dans un article similaire:

it1 = list1.iterator();
it2 = list2.iterator();
while(it1.hasNext() && it2.hasNext()) 
{
   value1 = it1.next();
   value2 = it2.next();

   doStuff(value1);
   doStuff(value2);
}//while

L'avantage pour l'itérateur est qu'il est générique, donc si vous ne savez pas quelles sont les collections utilisées, utilisez l'itérateur, sinon si vous connaissez vos collections, alors vous connaissez les fonctions de longueur / taille et donc la boucle for avec la contrainte supplémentaire peut être utilisé ici. (Notez que je suis très pluriel dans ce post comme une possibilité intéressante serait où les collections utilisées sont différentes, par exemple, l'une pourrait être une liste et l'autre un tableau par exemple)

J'espère que cela a aidé.


ArrayIterator vous permet d'éviter l'indexation, mais vous ne pouvez pas utiliser une boucle for-each sans écrire une classe séparée ou au moins une fonction. Comme le remarque @Alexei Blue, la recommandation officielle (à The Collection Interface ) est la suivante: «Utilisez Iterator place de la construction for-each lorsque vous devez: ... Itérer sur plusieurs collections en parallèle.»:

import static com.google.common.base.Preconditions.checkArgument;
import org.apache.commons.collections.iterators.ArrayIterator;

// …

  checkArgument(array1.length == array2.length);
  Iterator it1 = ArrayIterator(array1);
  Iterator it2 = ArrayIterator(array2);
  while (it1.hasNext()) {
      doStuff(it1.next());
      doOtherStuff(it2.next());
  }

Toutefois:

  • L'indexation est naturelle pour les tableaux - un tableau est par définition quelque chose que vous indexez, et une boucle numérique, comme dans votre code original, est parfaitement naturelle et plus directe.
  • Les paires valeur-clé forment naturellement une Map , comme le remarque @Isaac Truett, de sorte que la plus simple serait de créer des cartes pour tous vos tableaux parallèles (cette boucle ne serait donc que dans la fonction usine qui crée les cartes). vous voulez juste les parcourir. (Utilisez Multimap si vous devez prendre en charge les doublons.)
  • Si vous en avez beaucoup, vous pouvez implémenter (partiellement) ParallelArrayMap<> (par exemple, une carte sauvegardée par des tableaux parallèles), ou peut-être ParallelArrayHashMap<> (pour ajouter une HashMap si vous voulez une recherche efficace par clé) , qui permet l'itération dans l'ordre original. Ceci est probablement exagéré, mais permet une réponse sexy.

C'est:

Map<T, U> map = new ParallelArrayMap<>(array1, array2);
for (Map.Entry<T, U> entry : map.entrySet()) {
  doStuff(entry.getKey());
  doOtherStuff(entry.getValue());
}

Philosophiquement, le style Java doit avoir des types nommés explicites , implémentés par des classes. Donc, quand vous dites "[j'ai] tableaux parallèles [qui] stockent des paires clé / valeur.", Java répond "Ecrit une classe ParallelArrayMap qui implémente Map (paire clé / valeur) et qui a un constructeur qui prend des tableaux parallèles, puis vous pouvez utiliser entrySet pour renvoyer un Set que vous pouvez parcourir, puisque Set implémente Collection . "- rend la structure explicite dans un type, implémenté par une classe.

Pour itérer sur deux collections ou tableaux parallèles, vous voulez parcourir une Iterable<Pair<T, U>> , que les langages moins explicites vous permettent de créer avec zip (que @Isaac Truett appelle wrap ). Ce n'est pas Java idiomatique, cependant - quels sont les éléments de la paire? Voir Java: Comment écrire une fonction zip ? Quel devrait être le type de retour? pour une discussion approfondie sur la façon d'écrire cela en Java et pourquoi il est découragé.

C'est exactement le compromis stylistique que fait Java: vous savez exactement de quel type il s'agit, et vous devez le spécifier et l'implémenter.





iteration