【发布时间】:2011-11-02 07:03:18
【问题描述】:
在 Python 中,enumerate 函数允许您遍历 (index, value) 对的序列。例如:
>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
... print i, s
...
0 zero
1 one
2 two
在 Java 中有什么方法可以做到这一点吗?
【问题讨论】:
在 Python 中,enumerate 函数允许您遍历 (index, value) 对的序列。例如:
>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
... print i, s
...
0 zero
1 one
2 two
在 Java 中有什么方法可以做到这一点吗?
【问题讨论】:
对于实现List接口的集合,可以调用listIterator()方法获取ListIterator。迭代器(除其他外)有两种方法 - nextIndex(),用于获取索引;和next(),获取值(与其他迭代器一样)。
所以上面 Python 的 Java 等价物可能是:
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());
}
与 Python 一样,输出:
0 zero
1 one
2 two
【讨论】:
it.next() 有副作用吗?在同一个表达式中混合it.nextIndex() 和it.next() 是否保证安全?
next() 具有将迭代器推进一个元素的副作用。但是,Java 语言规范保证 + 运算符的操作数是从左到右计算的。见section 15.7。
enumerate 的工作方式完全不同。 python 的enumerate 将独立于其内部索引状态索引任意序列。它产生一个以 (index, element) 对作为元素的“替代”可迭代序列。它接受一个start 参数,该参数为索引添加偏移量 - 可以在循环中完成,但仍然可以。它与 for-each like 循环一起工作。
我发现这与 python 方法最相似。
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);
}
}
0 zero
1 one
2 two
3 zero
4 one
5 two
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);
}
}
【讨论】:
严格来说不可以,因为 Python 中的 enumerate() 函数返回的是元组列表,而 Java 中不存在元组。
但是,如果您感兴趣的只是打印一个索引和一个值,那么您可以按照 Richard Fearn 的建议并在迭代器上使用 nextIndex() 和 next()。
还要注意 enumerate() 可以使用更通用的 zip() 函数(使用 Python 语法)来定义:
mylist = list("abcd")
zip(range(len(mylist)), mylist)
给出 [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]
如果您定义自己的 Tuple 类(请参阅Using Pairs or 2-tuples in Java 作为起点),那么您当然可以轻松地用 Java 编写自己的 zip() 函数来使用它(使用链接中定义的 Tuple 类) :
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;
}
一旦有了 zip(),实现 enumerate() 就很简单了。
编辑:工作缓慢的一天,所以结束它:
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);
}
编辑 2:正如该问题的 cmets 所指出的,这并不完全等同。虽然它产生与 Python 的枚举相同的值,但它并没有以与 Python 的枚举相同的生成方式这样做。因此,对于大型馆藏,这种方法可能会让人望而却步。
【讨论】:
zip 和 range 创建的列表在非常大的列表上内存效率低下。迭代器和 enumerate 一样,只处理当前元素和生成下一个元素的函数。在 python 2.x 中,itertools.izip 和 xrange 更接近于模拟 enumerate。
根据 Python 文档 (here),这是使用 Java 可以获得的最接近的方法,并且不再冗长:
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]);
}
如果您需要使用List 类...
List<String> numbers = Arrays.asList("zero", "one", "two");
for (int i = 0; i < numbers.size(); i++) {
System.out.println(i + " " + numbers.get(i));
}
*注意:如果您需要在遍历列表时修改列表,则需要使用 Iterator 对象,因为它能够在不引发 ConcurrentModificationException 的情况下修改列表。
【讨论】:
简单明了
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++;
}
}
示例用法:
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));
});
}
【讨论】:
现在使用 Java 8s Stream API 以及提供 StreamUtils 的小型 ProtonPack 库可以轻松实现。
第一个示例使用与问题中相同的 for-each 表示法:
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());
}
尽管没有像 Python 那样提供惰性求值。
为此,您必须使用forEach() Stream API 方法:
Stream<String> numbers = Arrays.stream("zero one two".split(" "));
StreamUtils.zipWithIndex(numbers)
.forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
惰性求值可以通过以下无限流进行验证:
Stream<Integer> infStream = Stream.iterate(0, i -> i++);
StreamUtils.zipWithIndex(infStream)
.limit(196)
.forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
【讨论】:
List<String> list = { "foo", "bar", "foobar"};
int i = 0;
for (String str : list){
System.out.println(i++ + str );
}
【讨论】:
i++ 不是一个好的选择,因为如果该语句(有条件地)跳过或执行多次/复制,它可能会导致难以跟踪的错误.最好有一个专门的i++ 线路。
没有。也许有一些库可以支持这种功能。但是,如果您求助于标准库,那么您的工作就是数数。
【讨论】:
我认为这应该是最类似于python“枚举”的java功能,尽管它相当复杂且效率低下。基本上,只需使用 ListIterator 或 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());
}
或使用 lambda 表达式:
Set<Integer, String> enumeration = IntStream.range(0, list.size()).boxed.collect(Collectors.toMap(index -> index, index -> list.get(index)));
然后您可以将它与增强的 for 循环一起使用:
for (Map.Entry<Integer, String> entry : enumeration.entrySet){
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
【讨论】:
通过将泛型与匿名接口相结合,您基本上可以创建一个工厂方法来处理枚举。 Enumerator 回调隐藏了下面迭代器的混乱。
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);
}
});
}
}
[0] John
[1] Paul
[2] George
[3] Ringo
我更进一步,基于这个概念创建了 map、reduce 和 filter 函数。
Google 的 Guava 和 Apache common-collections 依赖项都包含类似的功能。您可以随意查看。
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));
}
}
【讨论】:
使用 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);
});
【讨论】: