RSS-Feed anzeigen

Landei

Erweiterte For-Schleife mit Index

Bewerten
Wie oft braucht man den Index der Elemente eines Iterables und hat dann nur die Wahl zwischen...

Java Code:
  1.  
  2. List<String> names = ...
  3. for(int i = 0; i < names.size(); i++) {
  4.    System.out.println((i+1) + ".\t" + names.get(i));
  5. }

... und ...

Java Code:
  1.  
  2. List<String> names = ...
  3. int i = 0;
  4. for(String name : names) {
  5.    System.out.println((++i) + ".\t" + name);
  6. }

Doch das ist eigentlich nur die Wahl zwischen Pest und Cholera: Entweder man hantiert umständlich mit Indexen und hat eventuell auch einen sehr "teuren" indizierten Zugriff (man denke an LinkedList). Oder man lässt den Index "nebenher" laufen, der dann außerhalb der Schleife definiert werden muss, und dort entsprechend sichtbar ist. Dann muss man auch aufpassen, bei der manuellen Erhöhung des Indexes nichts falsch zu machen (etwa Prefix vs Postfix-Notation, oder bei Umstellungen im Schleifenrumpf).

Keine Ahnung, warum es für dieses so einfache wie verbreitete Problem keine "offizielle" Helferklasse gibt - andere Sprachen sind da weiter (etwa Scala mit zipWithIndex). Hier einmal ein bescheidener Vorschlag:

Java Code:
  1.  
  2. import java.util.Iterator;
  3.  
  4. public class Indexed<A> {
  5.  
  6.     public final A value;
  7.     public final int index;
  8.  
  9.     public Indexed(A value, int index) {
  10.         this.value = value;
  11.         this.index = index;
  12.     }
  13.  
  14.     public static <A> Iterable<Indexed<A>> of(A... as) {
  15.         return () -> new Iterator<Indexed<A>>() {
  16.             private int i = 0;
  17.  
  18.             @Override
  19.             public boolean hasNext() {
  20.                 return i < as.length;
  21.             }
  22.  
  23.             @Override
  24.             public Indexed<A> next() {
  25.                 return new Indexed<>(as[i], i++);
  26.             }
  27.         };
  28.     }
  29.  
  30.     public static <A> Iterable<Indexed<A>> of(Iterable<A> iterable) {
  31.         return () -> new Iterator<Indexed<A>>() {
  32.             private Iterator<A> peer = iterable.iterator();
  33.             private int i = 0;
  34.  
  35.             @Override
  36.             public boolean hasNext() {
  37.                 return peer.hasNext();
  38.             }
  39.  
  40.             @Override
  41.             public Indexed<A> next() {
  42.                 return new Indexed<>(peer.next(), i++);
  43.             }
  44.         };
  45.     }
  46. }

Das ist auch schon alles was man braucht, und dabei habe ich auch noch eine extra Methode für Arrays spendiert.

Java Code:
  1.  
  2. //Anwendung für Iterables
  3. List<String> names = Arrays.asList("Heinz","Karl","Otto");
  4. for(Indexed<String> name : Indexed.of(names)) {
  5.     System.out.println((name.index+1) + ".\t" + name.value);
  6. }
  7.  
  8. //Anwendung für einzelne Werte und Arrays
  9. for(Indexed<String> name : Indexed.of("Heinz","Karl","Otto")) {
  10.     System.out.println((name.index+1) + ".\t" + name.value);
  11. }

Zugegeben, im Schleifenkopf geht es jetzt etwas gedrängter zu, und wie gewohnt kann man sich auch prächtig über Details streiten (z.B. ob öffentliche finalen Felder in Indexed in Ordnung sind, oder man doch lieber die länglichere Getter-Version bevorzugt).

Der große Vorteil ist aber, dass der Index einfach da ist, und man bei der Benutzung buchstäblich nichts falsch machen oder vergessen kann.

Wie handhabt ihr das Problem, und gibt es vielleicht noch bessere Lösungen?

Aktualisiert: 28.10.2015 um 15:17 von Landei

Stichworte: iterator, java
Kategorien
Kategorielos

Kommentare

  1. Avatar von nillehammer
    Wie handhabt ihr das Problem, und gibt es vielleicht noch bessere Lösungen?
    Ich lasse den Index "nebenher laufen". Finde es auch nicht so schwierig, die manuelle Erhöhung richtig zu machen. Sehe da also nicht den dringenden Handlungsbedarf.

    Gut finde ich an Deiner Lösung:
    1. Ich wusste gar nicht, dass man auch nicht-funktionale Interfaces lambdamäßig implementieren kann.
    2. Die Behandlung des Index ist tatsächlich elegant gelöst.

    Schlecht finde ich:
    1. Die Erzeugung eines neuen Objekts für JEDES Element der Iteration.
    Könnte man durch zwischenspeichern des Iterators umgehen. Der kennt den aktuellen index und man kann ihn sich von da holen. Das allerdings um den Preis, dass man for-each nicht mehr verwenden kann (also auch wieder Pest und Cholera).
  2. Avatar von Landei
    Es ist die Frage, ob die Index-Objekte wirklich erzeugt werden, die Escape-Analyse des Compilers ist recht clever. Es wird trotzdem wirklich Zeit, dass Java Tuple-Typen ohne Instantiierungs-Overhead unterstützt.
  3. Avatar von Crian
    Vor dieser Frage stehe ich auch immer wieder. Ein Konstrukt, dass einem bei der for-each-Schleife irgendwie nebenher den Index mitliefert, wäre wirklich gut.

    Hier stört mich der angewachsene Schleifenkopf, du hast es ja selbst auch schon gesagt.

    Die Frage ist auch, wie bei Dingen ohne Sortierung (ich denke da an das KeySet eines Hashes oder Sets generell) mit dem Index verfahren werden sollte. Zumindest meine ich im Hinterkopf zu haben, dass man sich auf die Sortierung der Schlüssel in einem Hash nicht verlassen darf und davon ausgehen muss, dass diese mal so und mal so aussieht.
  4. Avatar von Landei
    Ich habe ja inzwischen eine neue Variante gepostet, wenn auch etwas "hacky": https://forum.byte-welt.net/members/...-index-ii.html
  5. Avatar von Sen-Mithrarin
    Ich muss mal wieder den bösen Grammatik-N**i raushängen lassen: es heist zwar pre- und post- in-/de-crement - aber präfix und suffix =P
Kommentar schreiben Kommentar schreiben

Trackbacks