__iter__ - ¿Hay un equivalente en Java de la función de "enumerar" de Python?
python generator (10)
Ahora con Java 8s Stream API junto con la pequeña biblioteca StreamUtils
que proporciona StreamUtils
se puede lograr fácilmente.
El primer ejemplo usa la misma notación para cada una de las preguntas:
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());
}
Arriba, aunque no proporciona la evaluación perezosa como en Python. Para eso debes usar el método de la API de la secuencia forEach()
:
Stream<String> numbers = Arrays.stream("zero one two".split(" "));
StreamUtils.zipWithIndex(numbers)
.forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
La evaluación perezosa se puede verificar con el siguiente flujo infinito:
Stream<Integer> infStream = Stream.iterate(0, i -> i++);
StreamUtils.zipWithIndex(infStream)
.limit(196)
.forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
En Python, la función de enumerate
permite iterar sobre una secuencia de pares (índice, valor). Por ejemplo:
>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
... print i, s
...
0 zero
1 one
2 two
¿Hay alguna forma de hacer esto en Java?
Al combinar los genéricos con interfaces anónimas, esencialmente puede crear un método de fábrica para administrar la enumeración. La devolución de llamada del Enumerador oculta el desorden del iterador que se encuentra debajo.
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);
}
});
}
}
Resultado
[0] John
[1] Paul
[2] George
[3] Ringo
Pensamientos extendidos
Mapa, Reducir, Filtrar
He dado un paso más allá y he creado funciones de mapa, reducción y filtro basadas en este concepto.
Las dependencias de las colecciones comunes de Guava y Apache Google incluyen una funcionalidad similar. Puedes consultarlos como desees.
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));
}
}
Creo que esta debería ser la funcionalidad java que se asemeja a la "enumeración" de python, aunque es bastante complicada e ineficiente. Básicamente, simplemente asigne los índices de la lista a sus elementos, utilizando ListIterator o Collector:
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());
}
o usando la expresión lambda:
Set<Integer, String> enumeration = IntStream.range(0, list.size()).boxed.collect(Collectors.toMap(index -> index, index -> list.get(index)));
entonces puedes usarlo con un bucle for mejorado:
for (Map.Entry<Integer, String> entry : enumeration.entrySet){
System.out.println(entry.getKey() + "/t" + entry.getValue());
}
De acuerdo con los documentos de Python ( here ), esto es lo más cercano que puedes conseguir con Java, y no es más detallado:
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]);
}
Si necesitas usar la clase List
...
List<String> numbers = Arrays.asList("zero", "one", "two");
for (int i = 0; i < numbers.size(); i++) {
System.out.println(i + " " + numbers.get(i));
}
* NOTA: si necesita modificar la lista a medida que la recorre, deberá usar el objeto Iterator, ya que tiene la capacidad de modificar la lista sin generar una ConcurrentModificationException
.
Estrictamente hablando, no, ya que la función enumerate () en Python devuelve una lista de tuplas, y las tuplas no existen en Java.
Sin embargo, si todo lo que le interesa es imprimir un índice y un valor, puede seguir la sugerencia de Richard Fearn y usar nextIndex () y next () en un iterador.
Tenga en cuenta también que enumerate () se puede definir utilizando la función zip () más general (usando la sintaxis de Python):
mylist = list("abcd")
zip(range(len(mylist)), mylist)
da [(0, ''a''), (1, ''b''), (2, ''c''), (3, ''d'')]
Si define su propia clase Tuple (consulte Uso de pares o 2-tuplas en Java como punto de partida), puede escribir su propia función zip () en Java para usarla (utilizando la clase Tuple definida en la enlazar):
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;
}
Y una vez que tienes zip (), implementar enumerate () es trivial.
Edición: día lento en el trabajo, así que para terminar:
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);
}
Edición 2: como se señala en los comentarios a esta pregunta, esto no es completamente equivalente. Si bien produce los mismos valores que la enumeración de Python, no lo hace de la misma manera generativa que la enumeración de Python. Así, para grandes colecciones, este enfoque podría ser bastante prohibitivo.
Me parece que este es el más parecido al enfoque de Python.
Uso
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);
}
}
Salida
0 zero
1 one
2 two
3 zero
4 one
5 two
Caracteristicas
- Funciona en cualquier iterable
- No crea una copia de lista en memoria (adecuada para listas grandes)
- Soporta nativo para cada sintaxis.
- Acepta un parámetro de inicio que se puede agregar al índice
Implementación
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);
}
}
No. Tal vez hay algunas bibliotecas para soportar tal funcionalidad. Pero si recurres a las bibliotecas estándar, es tu trabajo contar.
Para las colecciones que implementan la interfaz de List
, puede llamar al método listIterator()
para obtener un ListIterator
. El iterador tiene (entre otros) dos métodos: nextIndex()
, para obtener el índice; y next()
, para obtener el valor (como otros iteradores).
Entonces, un equivalente de Java del Python anterior podría ser:
List<String> numbers = Arrays.asList("zero", "one", "two");
ListIterator<String> it = numbers.listIterator();
while (it.hasNext()) {
System.out.println(it.nextIndex() + " " + it.next());
}
que, como el Python, produce:
0 zero
1 one
2 two
Sencillo y directo.
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++;
}
}
Uso de la muestra:
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));
});
}
List<String> list = { "foo", "bar", "foobar"};
int i = 0;
for (String str : list){
System.out.println(i++ + str );
}