简体   繁体   English

是否有Java 8等效的Python枚举内置?

[英]Is there a Java 8 equivalent of Python enumerate built-in?

3 years ago a similar question was asked here: Is there a Java equivalent of Python's 'enumerate' function? 3年前,这里也提出了一个类似的问题: 是否存在类似于Python的'枚举'函数的Java?

I really appreciate the listIterator() solution. 我非常感谢listIterator()解决方案。 Still, I work a lot with the new streams and lambdas (introduced in JDK 8) nowadays and wonder: is there an elegant way of obtaining the index of the element being currently processed? 不过,我现在对新流和lambdas(在JDK 8中引入)做了很多工作并且想知道:有没有一种优雅的方法来获取当前处理的元素的索引? Mine is presented below, but I do not find it especially appealing. 我的展示如下,但我觉得它并不特别吸引人。

IntStream.range(0, myList.size())
         .mapToObj(i -> doSthWith(myList.get(i), i));

This question has been asked a few ways before. 之前已经提出过几个问题。 The key observation is that, unless you have perfect size and splitting information (basically, if your source is an array), then this would be a sequential-only operation. 关键的观察是,除非你有完美的大小和拆分信息(基本上,如果你的源是一个数组),那么这将是一个仅序贯操作。

The "unappealing" answer you propose: 你建议的“没有吸引力”的答案:

IntStream.range(0, myList.size())
         .mapToObj(i -> doSthWith(myList.get(i), i));

is actually quite efficient when myList is an ArrayList or other list with a fast O(1) indexed-get operation, and parallelizes cleanly. myList是一个ArrayList或其他具有快速O(1)索引获取操作的列表时,实际上是非常有效的,并且干净地并行化。 So I think there's nothing wrong with it. 所以我觉得它没有错。

There can be some workarounds to this. 可以有一些解决方法。

You can use an AtomicInteger that you will increment each time you go through an element of the stream 您可以使用每次遍历流的元素时将递增的AtomicInteger

final AtomicInteger atom = new AtomicInteger(); 
list.stream().forEach(i -> System.out.println(i+"-"+atom.getAndIncrement()));

Or using an iterator from another stream for the indexes (a bit like your original idea) but more efficient as you don't call get on the list. 或者使用来自另一个流的迭代器来获取索引(有点像你最初的想法),但更高效,因为你没有在列表上调用get

final Iterator<Integer> a = IntStream.range(0, list.size()).iterator();
list.stream().forEach(i -> System.out.println(i+"-"+a.next()));

Well I'm not sure is there exist other nicer alternatives to this (certainly) but that's what I think for the moment. 嗯,我不确定是否存在其他更好的替代品(当然),但这就是我现在的想法。

Note that it's assuming that you are not using a parallel stream. 请注意,它假设您没有使用并行流。 In the latter case that would not be possible to do it like that to obtain a mapping of the elements with their original indexes in the list. 在后一种情况下,不可能这样做以获得元素与列表中的原始索引的映射。

If you want a solution that works with non- RandomAccess lists as well, you can write yourself a simple utility method: 如果您想要一个适用于非RandomAccess列表的解决方案,您可以自己编写一个简单的实用工具方法:

public static <T> void forEach(List<T> list, BiConsumer<Integer,? super T> c) {
    Objects.requireNonNull(c);
    if(list.isEmpty()) return;
    if(list instanceof RandomAccess)
        for(int i=0, num=list.size(); i<num; i++)
            c.accept(i, list.get(i));
    else
        for(ListIterator<T> it=list.listIterator(); it.hasNext(); ) {
            c.accept(it.nextIndex(), it.next());
    }
}

Then you can use it like: forEach(list, (i,s)->System.out.println(i+"\\t"+s)); 然后你就可以使用它: forEach(list, (i,s)->System.out.println(i+"\\t"+s));


If you swap the order of element and index, you can use an ObjIntConsumer instead of BiConsumer<Integer,? super T> 如果交换元素和索引的顺序,可以使用ObjIntConsumer而不是BiConsumer<Integer,? super T> BiConsumer<Integer,? super T> to avoid potential boxing overhead: BiConsumer<Integer,? super T>以避免潜在的拳击开销:

public static <T> void forEach(List<T> list, ObjIntConsumer<? super T> c) {
    Objects.requireNonNull(c);
    if(list.isEmpty()) return;
    if(list instanceof RandomAccess)
        for(int i=0, num=list.size(); i<num; i++)
            c.accept(list.get(i), i);
    else
        for(ListIterator<T> it=list.listIterator(); it.hasNext(); ) {
            c.accept(it.next(), it.previousIndex());
    }
}

Then, to be used like forEach(list, (s,i) -> System.out.println(i+"\\t"+s)); 然后,像forEach(list, (s,i) -> System.out.println(i+"\\t"+s));一样forEach(list, (s,i) -> System.out.println(i+"\\t"+s)); ...

The python example in the link you provided is: 您提供的链接中的python示例是:

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

There are several ways to achieve the same output with Java 8, for example: 有几种方法可以使用Java 8实现相同的输出,例如:

String[] numbers = {"zero", "one", "two"};
IntStream.range(0, numbers.length)
        .mapToObj(i -> i + " " + numbers[i])
        .forEach(System.out::println);

which outputs: 哪个输出:

0 zero
1 one
2 two

If you want cleaner syntax, then just wrap up some of your calls: 如果你想要更清晰的语法,那么只需结束一些调用:

public class IndexedValue<T> {
    public final int index;
    public final T value;
    public IndexedValue(int index, T value) {
        this.index = index;
        this.value = value;
    }
}

public class Itertools {
    public static <T> Stream<IndexedValue<T>> enumerate(ArrayList<T> lst) {
        return IntStream.range(0, lst.size())
            .mapToObj(i -> new IndexedValue<T>(i, lst.get(i)));
    }
}

Then using it: 然后使用它:

import static com.example.Itertools.enumerate;

enumerate(myList).map(i -> doSthWith(i.value, i.index));

If you want an efficient LinkedList solution, then you'll need to handle that without making calls to LinkedList.get(). 如果你想要一个高效的LinkedList解决方案,那么你需要在不调用LinkedList.get()的情况下处理它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM