有没有类似 Python 的“枚举”函数的 Java 等价物?

在 Python 中,enumerate函数允许您在一个(索引,值)对序列上进行迭代:

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

在 Java 中有什么方法可以做到这一点吗?

37117 次浏览

For collections that implement the List interface, you can call the listIterator() method to get a ListIterator. The iterator has (amongst others) two methods - nextIndex(), to get the index; and next(), to get the value (like other iterators).

So a Java equivalent of the Python above might be:

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


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

which, like the Python, outputs:

0 zero
1 one
2 two

No. Maybe there are some libraries for supporting such a functionality. But if you resort to the standard libraries it is your job to count.

List<String> list = { "foo", "bar", "foobar"};
int i = 0;
for (String str : list){
System.out.println(i++ + str );
}

Strictly speaking, no, as the enumerate() function in Python returns a list of tuples, and tuples do not exist in Java.

If however, all you're interested in is printing out an index and a value, then you can follow the suggestion from Richard Fearn & use nextIndex() and next() on an iterator.

Note as well that enumerate() can be defined using the more general zip() function (using Python syntax):

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

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

If you define your own Tuple class (see Using Pairs or 2-tuples in Java as a starting point), then you could certainly easily write your own zip() function in Java to make use of it (using the Tuple class defined in the link):

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

And once you have zip(), implementing enumerate() is trivial.

Edit: slow day at work, so to finish it off:

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: as pointed out in the comments to this question, this is not entirely equivalent. While it produces the same values as Python's enumerate, it doesn't do so in the same generative fashion that Python's enumerate does. Thus for large collections this approach could be quite prohibitive.

According to the Python docs (here), this is the closest you can get with Java, and it's no more verbose:

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

If you need to use the List class...

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

*NOTE: if you need to modify the list as you're traversing it, you'll need to use the Iterator object, as it has the ability to modify the list without raising a ConcurrentModificationException.

I think this should be the java functionality that resemble the python "enumerate" most, though it is quite complicated and inefficent. Basically, just map the list's indices to its elements, using ListIterator or 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());
}

or using lambda expression:

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

then you can use it with an enhanced for loop:

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

By combining generics with anonymous interfaces, you can essentially create a factory method for handing enumeration. The Enumerator callback hides the messiness of the iterator underneath.

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

Result

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

Extended Thoughts

Map, Reduce, Filter

I have taken this a step further and created map, reduce, and filter functions based on this concept.

Both Google's Guava and Apache common-collections dependencies include similar functionality. You can check them out as you wish.

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

I find this to be the most similar to the python approach.

Usage

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

Output

0 zero
1 one
2 two


3 zero
4 one
5 two

Features

  • Works on any iterable
  • Does not create an in-memory list copy (suitable for large lists)
  • Supports native for each syntax
  • Accepts a start parameter which can be added to the index

Implementation

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


}

Now with Java 8s Stream API together with the small ProtonPack library providing StreamUtils it can be achieved easily.

The first example uses the same for-each notation as in the question:

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

Above although does not provide the lazy evaluation as in Python. For that you must use the forEach() Stream API method:

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

The lazy evaluation can be verified with the following infinite stream:

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

Simple and straightforward

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

Sample usage:

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

Pretty much the same syntax using 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);
});