Java中是否有类似于Python的'enumerate'函数的等效函数?

96
在Python中,enumerate 函数允许您迭代一个序列的(索引,值)对。例如:
>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
...     print i, s
... 
0 zero
1 one
2 two

有没有在Java中实现这个的方法?
11个回答

81

对于实现了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

6
it.next()有副作用吗?在同一表达式中混合使用it.nextIndex()it.next()是否保证安全? - John La Rooy
2
是的,它会跳转到下一个元素。请参阅http://download.oracle.com/javase/6/docs/api/java/util/ListIterator.html 了解ListIterator的工作原理。 - JB Nizet
4
正如@JB Nizet所说,是的,next()具有将迭代器向前移动一个元素的副作用。然而,Java语言规范保证+运算符的操作数从左到右进行求值。请参见第 15.7 节 - Richard Fearn
8
这是一种“替代方案”。enumerate的工作方式完全不同。Python的enumerate会独立于内部索引状态为任意序列进行索引。它生成一个“替代”的可迭代序列,其中元素为(index, element)对。它接受一个start参数,该参数将偏移量添加到索引上 - 可以在循环中完成但仍然有效。它与类似for-each的循环本地兼容。 - n611x007

24
我认为这与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

特点

  • 适用于任何可迭代对象
  • 不会创建内存中的列表副本(适用于大型列表)
  • 支持原生的 for each 语法
  • 接受一个起始参数,可以添加到索引中

实现

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

}

2
我正在考虑实现这种东西。我认为这是标准库的一个很好的候选项。 - dantiston

11

严格来说,在 Java 中不存在元组,因此 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类(请参见在Java中使用Pairs或2-tuples作为起点),那么您肯定可以轻松编写自己的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:正如在此问题的评论中所指出的那样,这并不完全等同。虽然它产生了与Python的枚举相同的值,但它并没有像Python的枚举那样以相同的生成方式执行。因此,对于大型集合,这种方法可能会受到限制。


我认为从技术上讲,使用一个初始化为两个输入列表长度的Math.min的ArrayList会是返回列表的更好选择,但这个想法是一样的。 - Adam Parkin
1
然而,这是不准确的:Python 的 enumerate返回元组列表。它返回一个“枚举对象”,该对象是可迭代的,因为 enumerate设计成一个生成器 - n611x007
ziprange创建的列表在非常大的列表上内存效率低下。像enumerate这样的迭代器只处理当前元素和生成下一个元素的函数。在Python 2.x中,有itertools.izipxrange更接近于模拟enumerate - n611x007
@naxa:说得对,从效率的角度来看它们并不等价,但从最终输出的角度来看它们是等价的。我会更新答案。 - Adam Parkin

8

简单而直观

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

5
根据Python文档(这里),以下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));
}

*注意:如果您需要在遍历列表时修改它,您需要使用迭代器对象,因为它具有在不引发ConcurrentModificationException的情况下修改列表的能力。


1
这种链表的方法不太理想,因为查找时间较慢。 - roozbeh sharifnasab

3
现在,使用Java 8的Stream API和小型ProtonPack库提供的StreamUtils,可以轻松实现此功能。
第一个示例使用与问题中相同的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());
}

尽管Java中没有像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()));

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

循环末尾缺少 i++。初始化列表的语法无效。您必须使用 Arrays.asList(...)。 - JB Nizet
1
@JB Nizet:是的,谢谢。我正在编辑它。我认为我可以直接在println中使用i++,因为i的值在返回后应该被递增。 - Heisenbug
1
在我看来,在语句中使用 i++ 不是一个好的选择,因为如果该语句被(有条件地)跳过或执行多次/复制,它可能会导致难以追踪的错误。最好有一个专门的 i++ 行。 - tobias_k

2

不行。也许有一些支持这种功能的库。但如果您使用标准库,数数就是您的工作。


1
RichardFearn的工作方式非常不同,尽管它可以用于相同的目的。 - n611x007

1

我认为这应该是最接近Python“enumerate”的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());
}

1
通过将泛型与匿名接口结合,您可以基本上创建一个用于处理枚举的工厂方法。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

拓展思考

映射、归约、过滤器

我已经进一步地基于这个概念创建了映射、归约和过滤器函数。

谷歌的GuavaApache常用集合依赖库都包含类似的功能。您可以根据需要查看它们。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接