Gibt es ein Java-Äquivalent zu Pythons Aufzählungsfunktion?

74

In Python enumeratekönnen Sie mit dieser Funktion eine Folge von (Index-, Wert-) Paaren durchlaufen. Zum Beispiel:

>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
...     print i, s
... 
0 zero
1 one
2 two

Gibt es eine Möglichkeit, dies in Java zu tun?

Richard Fearn
quelle

Antworten:

59

Für Sammlungen, die die ListSchnittstelle implementieren , können Sie die listIterator()Methode aufrufen , um eine zu erhalten ListIterator. Der Iterator hat (unter anderem) zwei Methoden - nextIndex()um den Index zu erhalten; und next(), um den Wert zu erhalten (wie bei anderen Iteratoren).

Ein Java-Äquivalent zu Python könnte also sein:

List<String> numbers = Arrays.asList("zero", "one", "two");
ListIterator<String> it = numbers.listIterator();
while (it.hasNext()) {
    System.out.println(it.nextIndex() + " " + it.next());
}

was wie der Python ausgibt:

0 zero
1 one
2 two
Richard Fearn
quelle
4
it.next()Hat also ein Nebeneffekt? Ist es garantiert sicher zu mischen it.nextIndex()und it.next()im gleichen Ausdruck?
John La Rooy
2
Ja, es geht zum nächsten Element. Siehe download.oracle.com/javase/6/docs/api/java/util/... , wie ein ListIterator funktioniert.
JB Nizet
4
Wie @JB Nizet sagt, hat ja next()den Nebeneffekt, dass der Iterator um ein Element erweitert wird. Die Java-Sprachspezifikation garantiert jedoch, dass die Operanden für den +Operator von links nach rechts ausgewertet werden. Siehe Abschnitt 15.7 .
Richard Fearn
7
Dies ist eine Alternative . enumeratefunktioniert ganz anders. Pythons enumerateindiziert eine beliebige Sequenz unabhängig von ihrem internen Indexstatus. Es ergibt eine iterierbare 'Ersatz'-Sequenz mit (Index-, Element-) Paaren als Elementen. Es akzeptiert einen startParameter, der dem Index einen Offset hinzufügt - kann aber trotzdem in der Schleife ausgeführt werden. Es funktioniert nativ mit den for-each like-Schleifen.
n611x007
15

Ich finde, dass dies dem Python-Ansatz am ähnlichsten ist.

Verwendung

public static void main(String [] args) {
    List<String> strings = Arrays.asList("zero", "one", "two");
    for(EnumeratedItem<String> stringItem : ListUtils.enumerate(strings)) {
        System.out.println(stringItem.index + " " + stringItem.item);
    }
    System.out.println();
    for(EnumeratedItem<String> stringItem : ListUtils.enumerate(strings, 3)) {
        System.out.println(stringItem.index + " " + stringItem.item);
    }
}

Ausgabe

0 zero
1 one
2 two

3 zero
4 one
5 two

Eigenschaften

  • Funktioniert auf jedem iterable
  • Erstellt keine speicherinterne Listenkopie (geeignet für große Listen)
  • Unterstützt native für jede Syntax
  • Akzeptiert einen Startparameter, der dem Index hinzugefügt werden kann

Implementierung

import java.util.Iterator;

public class ListUtils {

    public static class EnumeratedItem<T> {
        public T item;
        public int index;

        private EnumeratedItem(T item, int index) {
            this.item = item;
            this.index = index;
        }
    }

    private static class ListEnumerator<T> implements Iterable<EnumeratedItem<T>> {

        private Iterable<T> target;
        private int start;

        public ListEnumerator(Iterable<T> target, int start) {
            this.target = target;
            this.start = start;
        }

        @Override
        public Iterator<EnumeratedItem<T>> iterator() {
            final Iterator<T> targetIterator = target.iterator();
            return new Iterator<EnumeratedItem<T>>() {

                int index = start;

                @Override
                public boolean hasNext() {
                    return targetIterator.hasNext();
                }

                @Override
                public EnumeratedItem<T> next() {
                    EnumeratedItem<T> nextIndexedItem = new EnumeratedItem<T>(targetIterator.next(), index);
                    index++;
                    return nextIndexedItem;
                }

            };
        }

    }

    public static <T> Iterable<EnumeratedItem<T>> enumerate(Iterable<T> iterable, int start) {
        return new ListEnumerator<T>(iterable, start);
    }

    public static <T> Iterable<EnumeratedItem<T>> enumerate(Iterable<T> iterable) {
        return enumerate(iterable, 0);
    }

}
Tempo
quelle
1
Ich habe nur darüber nachgedacht, so etwas umzusetzen. Ich denke, dies ist ein großartiger Kandidat für die Standardbibliothek.
Dantiston
8

Genau genommen, nein, da die Funktion enumerate () in Python eine Liste von Tupeln zurückgibt und Tupel in Java nicht vorhanden sind.

Wenn jedoch alles , was Sie daran interessiert sind , ist das Drucken eines Index und einen Wert aus, dann können Sie den Vorschlag von Richard Fearn & Nutzung nextindex () folgen und next () auf einem Iterator.

Beachten Sie auch, dass enumerate () mit der allgemeineren Funktion zip () (mit Python-Syntax) definiert werden kann:

mylist = list("abcd")
zip(range(len(mylist)), mylist)

ergibt [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

Wenn Sie Ihre eigene Tupel- Klasse definieren (siehe Verwenden von Paaren oder 2-Tupeln in Java als Ausgangspunkt), können Sie sicherlich leicht Ihre eigene zip () -Funktion in Java schreiben, um sie zu verwenden (unter Verwendung der in der Tabelle definierten Tupel-Klasse) Verknüpfung):

public static <X,Y> List<Tuple<X,Y>> zip(List<X> list_a, List<Y> list_b) {
    Iterator<X> xiter = list_a.iterator();
    Iterator<Y> yiter = list_b.iterator();

    List<Tuple<X,Y>> result = new LinkedList<Tuple<X,Y>>();

    while (xiter.hasNext() && yiter.hasNext()) {
        result.add(new Tuple<X,Y>(xiter.next(), yiter.next()));
    }

    return result;
}

Und sobald Sie zip () haben, ist die Implementierung von enumerate () trivial.

Bearbeiten: langsamer Tag bei der Arbeit, um es zu beenden:

public static <X> List<Tuple<Integer,X>> enumerate (List<X> list_in) {
    List<Integer> nums = new ArrayList<Integer>(list_in.size());
    for (int x = 0; x < list_in.size(); x++) { 
        nums.add(Integer.valueOf(x));
    }

    return zip (nums, list_in);
}

Edit 2: Wie in den Kommentaren zu dieser Frage ausgeführt, ist dies nicht ganz gleichwertig. Es erzeugt zwar dieselben Werte wie die Aufzählung von Python, jedoch nicht auf dieselbe generative Weise wie die Aufzählung von Python. Daher könnte dieser Ansatz für große Sammlungen ziemlich unerschwinglich sein.

Adam Parkin
quelle
Ich nehme auch technisch an, dass die Verwendung einer ArrayList, die mit Math.min der beiden Längen der Eingabeliste initialisiert wurde, eine bessere Wahl für die zurückgegebene Liste wäre, aber die Idee ist dieselbe.
Adam Parkin
Es ist jedoch ungenau: Python enumerategibt keine Liste von Tupeln zurück. es gibt ein Auflisten Objekt ' , das ist iterable seit enumeratewurde entworfen a seinen Generator .
n611x007
zipund rangeerstellt eine Liste, die auf sehr großen Listen speichereffizient ist. Iteratoren wie Enumerate behandeln nur das aktuelle Element und eine Funktion, um das nächste zu generieren. In Python 2.x gibt es itertools.izip und xrange , die genauer emuliert werden können enumerate.
n611x007
@naxa: Fair genug, aus Sicht der Effizienz sind sie nicht gleichwertig, aber aus Sicht der endgültigen Ausgabe sind sie es. Aktualisiert die Antwort damit.
Adam Parkin
4

Laut den Python-Dokumenten ( hier ) ist dies das Beste, was Sie mit Java erreichen können, und es ist nicht mehr ausführlich:

String[] numbers = {"zero", "one", "two"}
for (int i = 0; i < numbers.length; i++) // Note that length is a property of an array, not a function (hence the lack of () )
    System.out.println(i + " " + numbers[i]);
}

Wenn Sie die ListKlasse verwenden müssen ...

List<String> numbers = Arrays.asList("zero", "one", "two");
for (int i = 0; i < numbers.size(); i++) {
    System.out.println(i + " " + numbers.get(i));
}

* HINWEIS: Wenn Sie die Liste während des Durchlaufens ändern müssen, müssen Sie das Iterator-Objekt verwenden, da es die Liste ändern kann, ohne ein zu erhöhen ConcurrentModificationException.

Travis
quelle
2
List<String> list = { "foo", "bar", "foobar"};
int i = 0;
for (String str : list){
     System.out.println(i++ + str );
}
Heisenbug
quelle
Am Ende der Schleife fehlt ein i ++. Die Syntax zum Initialisieren der Liste ist ungültig. Sie müssen Arrays.asList (...)
JB Nizet
1
@JB Nizet: Ja ... danke. Ich habe es bearbeitet. Ich denke, ich kann i ++ direkt im println verwenden, weil ich erhöht werden sollte, nachdem sein Wert zurückgegeben wurde
Heisenbug
1
IMHO i++ist es keine gute Wahl, die in einer der Anweisungen zu haben, da dies zu schwer zu verfolgenden Fehlern führen kann, wenn diese Anweisung (bedingt) mehr als einmal übersprungen oder ausgeführt / kopiert wird. Besser eine Standleitung haben i++.
tobias_k
2

Jetzt mit Java 8s Stream API zusammen mit der kleinen ProtonPackBibliothek, sofern StreamUtilses einfach erreicht werden kann.

Das erste Beispiel verwendet für jede Notation dieselbe wie in der Frage:

Stream<String> numbers = Arrays.stream("zero one two".split(" "));
List<Indexed<String>> indexedNumbers = StreamUtils.zipWithIndex(numbers)
                                                  .collect(Collectors.toList());
for (Indexed<String> indexed : indexedNumbers) {
    System.out.println(indexed.getIndex() + " " + indexed.getValue());
}

Oben liefert jedoch nicht die faule Auswertung wie in Python. Dazu müssen Sie die forEach()Stream-API-Methode verwenden:

Stream<String> numbers = Arrays.stream("zero one two".split(" "));
StreamUtils.zipWithIndex(numbers)
        .forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));

Die verzögerte Bewertung kann mit dem folgenden unendlichen Strom überprüft werden:

Stream<Integer> infStream = Stream.iterate(0, i -> i++);
StreamUtils.zipWithIndex(infStream)
        .limit(196)
        .forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
Sevenforce
quelle
2

Einfach und unkompliziert

public static <T> void enumerate(Iterable<T> iterable, java.util.function.ObjIntConsumer<T> consumer) {
    int i = 0;
    for(T object : iterable) {
        consumer.accept(object, i);
        i++;
    }
}

Beispielnutzung:

void testEnumerate() {
    List<String> strings = Arrays.asList("foo", "bar", "baz");
    enumerate(strings, (str, i) -> {
        System.out.println(String.format("Index:%d String:%s", i, str));
    });
}
balki
quelle
1

Nein. Vielleicht gibt es einige Bibliotheken, die eine solche Funktionalität unterstützen. Wenn Sie jedoch auf die Standardbibliotheken zurückgreifen, müssen Sie zählen.

Rocksportrocker
quelle
1
RichardFearns funktioniert ganz anders, obwohl es für das gleiche Ziel verwendet werden kann.
n611x007
1

Ich denke, dies sollte die Java-Funktionalität sein, die der Python-Aufzählung am meisten ähnelt, obwohl sie ziemlich kompliziert und ineffizient ist. Ordnen Sie die Indizes der Liste einfach mit ListIterator oder Collector ihren Elementen zu:

List<String> list = new LinkedList<>(Arrays.asList("one", "two", "three", "four"));
Map<Integer, String> enumeration = new Map<>();
ListIterator iter = list.listIterator();
while(iter.hasNext){
    map.put(iter.nextIndex(), iter.next());
}

oder mit Lambda-Ausdruck:

Set<Integer, String> enumeration = IntStream.range(0, list.size()).boxed.collect(Collectors.toMap(index -> index, index -> list.get(index)));

dann können Sie es mit einer erweiterten for-Schleife verwenden:

for (Map.Entry<Integer, String> entry : enumeration.entrySet){
    System.out.println(entry.getKey() + "\t" + entry.getValue());
}
charlieh_7
quelle
1

Durch die Kombination von Generika mit anonymen Schnittstellen können Sie im Wesentlichen eine Factory-Methode für die Übergabe von Aufzählungen erstellen. Der Enumerator-Rückruf verbirgt die Unordnung des darunter liegenden Iterators.

import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

public class ListUtils2 {
    public static interface Enumerator<T> {
        void execute(int index, T value);
    };

    public static final <T> void enumerate(final List<T> list,
            final Enumerator<T> enumerator) {
        for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
            enumerator.execute(it.nextIndex(), it.next());
        }
    }

    public static final void enumerate(final String[] arr,
            final Enumerator<String> enumerator) {
        enumerate(Arrays.asList(arr), enumerator);
    }

    public static void main(String[] args) {
        String[] names = { "John", "Paul", "George", "Ringo" };

        enumerate(names, new Enumerator<String>() {
            @Override
            public void execute(int index, String value) {
                System.out.printf("[%d] %s%n", index, value);
            }
        });
    }
}

Ergebnis

[0] John
[1] Paul
[2] George
[3] Ringo

Erweiterte Gedanken

Map, Reduce, Filter

Ich bin noch einen Schritt weiter gegangen und habe auf der Grundlage dieses Konzepts Funktionen zum Zuordnen, Reduzieren und Filtern erstellt.

Die allgemeinen Sammlungen von Google für Guava und Apache enthalten ähnliche Funktionen. Sie können sie nach Belieben auschecken.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

public class ListUtils {
    // =========================================================================
    // Enumerate
    // =========================================================================
    public static abstract interface Enumerator<T> {
        void execute(int index, T value, List<T> list);
    };

    public static final <T> void enumerate(final List<T> list,
            final Enumerator<T> enumerator) {
        for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
            enumerator.execute(it.nextIndex(), it.next(), list);
        }
    }

    // =========================================================================
    // Map
    // =========================================================================
    public static interface Transformer<T, U> {
        U execute(int index, T value, List<T> list);
    };

    public static final <T, U> List<U> transform(final List<T> list,
            final Transformer<T, U> transformer) {
        List<U> result = new ArrayList<U>();
        for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
            result.add(transformer.execute(it.nextIndex(), it.next(), list));
        }
        return result;
    }

    // =========================================================================
    // Reduce
    // =========================================================================
    public static interface Reducer<T, U> {
        U execute(int index, T value, U result, List<T> list);
    };

    public static final <T, U> U reduce(final List<T> list,
            final Reducer<T, U> enumerator, U result) {
        for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
            result = enumerator.execute(it.nextIndex(), it.next(), result, list);
        }
        return result;
    }

    // =========================================================================
    // Filter
    // =========================================================================
    public static interface Predicate<T> {
        boolean execute(int index, T value, List<T> list);
    };

    public static final <T> List<T> filter(final List<T> list,
            final Predicate<T> predicate) {
        List<T> result = new ArrayList<T>();
        for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
            int index = it.nextIndex();
            T value = it.next();
            if (predicate.execute(index, value, list)) {
                result.add(value);
            }
        }
        return result;
    }

    // =========================================================================
    // Predefined Methods
    // =========================================================================
    // Enumerate
    public static <T> String printTuples(List<T> list) {
        StringBuffer buff = new StringBuffer();

        enumerate(list, new Enumerator<T>() {
            @Override
            public void execute(int index, T value, List<T> list) {
                buff.append('(').append(index).append(", ")
                    .append(value).append(')');
                if (index < list.size() - 1) {
                    buff.append(", ");
                }
            }
        });

        return buff.toString();
    }

    // Map
    public static List<String> intToHex(List<Integer> list) {
        return transform(list, new Transformer<Integer, String>() {
            @Override
            public String execute(int index, Integer value, List<Integer> list) {
                return String.format("0x%02X", value);
            }
        });
    }

    // Reduce
    public static Integer sum(List<Integer> list) {
        return reduce(list, new Reducer<Integer, Integer>() {
            @Override
            public Integer execute(int index, Integer value, Integer result,
                    List<Integer> list) {
                return result + value;
            }
        }, 0);
    }

    // Filter
    public static List<Integer> evenNumbers(List<Integer> list) {
        return filter(list, new Predicate<Integer>() {
            @Override
            public boolean execute(int index, Integer value, List<Integer> list) {
                return value % 2 == 0;
            }
        });
    }

    // =========================================================================
    // Driver
    // =========================================================================
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(8, 6, 7, 5, 3, 0, 9);

        // Enumerate
        System.out.printf("%-10s: %s%n", "Enumerate", printTuples(numbers));

        // Map
        System.out.printf("%-10s: %s%n", "Map", intToHex(numbers));

        // Reduce
        System.out.printf("%-10s: %d%n", "Reduce", sum(numbers));

        // Filter
        System.out.printf("%-10s: %s%n", "Filter", evenNumbers(numbers));
    }
}
Mr. Polywhirl
quelle
-2

Ziemlich die gleiche Syntax mit Java8-Streams

    ArrayList<String> numbers = new ArrayList<String>();
    numbers.add("one");
    numbers.add("two");
    numbers.add("three");

    numbers.stream().forEach(num ->
    {
        System.out.println(numbers.indexOf(num) + " " + num);
    });
David Carracedo Martínez
quelle
Nicht wirklich; Dies erfordert das Durchlaufen der Liste bei jeder Iteration, um den Index des aktuellen Elements zu finden, und funktioniert nicht, wenn Elemente in der Liste dupliziert werden.
Richard Fearn