Java 8 lambda:将集合转换为元素映射,迭代位置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25041177/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 07:12:17  来源:igfitidea点击:

Java 8 lambda: convert Collection to Map of element, iteration position

javalambdajava-8java-stream

提问by danial

How do you convert a collection like ["a", "b", "c"] to a map like {"a": 0, "b":1, "c":2} with the values being the order of iteration. Is there a one liner with streams and collectors in JDK8 for it? Old fashion way is like this:

如何将 ["a", "b", "c"] 之类的集合转换为 {"a": 0, "b":1, "c":2} 之类的映射,其值的顺序为迭代。JDK8 中是否有一个带有流和收集器的衬垫?旧时尚的方式是这样的:

    Collection<String> col = apiCall();
    Map<String, Integer> map = new HashMap<>();
    int pos = 0;
    for (String s : collection) {
        map.put(s, pos++);
    }

回答by maba

It you don't need a parallel stream you can use the length of the map as an index counter:

如果您不需要并行流,您可以使用地图的长度作为索引计数器:

collection.stream().forEach(i -> map.put(i, map.size() + 1));

回答by Stuart Marks

Here's an approach:

这是一种方法:

List<String> list = Arrays.asList("a", "b", "c");

Map<String, Integer> map =
    IntStream.range(0, list.size())
        .boxed()
        .collect(toMap(i -> list.get(i), i -> i));

Not necessarily a one-liner or shorter than the straightforward loop, but it does work using a parallel stream if you change toMapto toConcurrentMap.

不一定是单行或比直接循环短,但如果您更改toMaptoConcurrentMap.

Also note, this assumes that you have a random-access list, not a general Collection. If you have a Collectionthat you otherwise can make no assumptions about, there's not much you can do other than to iterate over it sequentially and increment a counter.

另请注意,这假设您有一个随机访问列表,而不是一般的Collection. 如果你有一个Collection你不能做任何假设的,除了按顺序迭代它并增加一个计数器之外,你没有什么可以做的。

UPDATE

更新

The OP has clarified that the input is a Collectionand not a Listso the above doesn't apply. It seems that we can assume very little about the input Collection. The OP has specified iteration order. With a sequential iterator, the elements will come out in someorder although no guarantees can be made about it. It might change from run to run, or even from one iteration to the next (though this would be unusual in practice -- unless the underlying collection is modified).

OP 已澄清输入是 aCollection而不是 a,List因此上述内容不适用。似乎我们对输入的假设很少Collection。OP 已指定迭代顺序。使用顺序迭代器,元素将按某种顺序出现,尽管不能保证它。它可能会随着运行而变化,甚至从一个迭代到下一个迭代(尽管这在实践中是不寻常的——除非底层集合被修改)。

If the exact iteration order needs to be preserved, I don't believe there's a way to preserve it into the result Mapwithout iterating the input Collectionsequentially.

如果需要保留确切的迭代顺序,我认为没有办法在MapCollection按顺序迭代输入的情况下将其保留到结果中。

If, however, the exact iteration order isn't important, and the requirement is that the output Maphave unique values for each input element, then it would be possible to do something like this in parallel:

但是,如果确切的迭代顺序并不重要,并且要求输出Map对于每个输入元素都具有唯一值,那么可以并行执行以下操作:

Collection<String> col = apiCall();
Iterator<String> iter = col.iterator();

Map<String, Integer> map =
    IntStream.range(0, col.size())
        .parallel()
        .boxed()
        .collect(toConcurrentMap(i -> { synchronized (iter) { return iter.next(); }},
                                 i -> i));

This is now farfrom a one-liner. It's also not clear to me how useful it is. :-) But it does demonstrate that it's possible to do something like this in parallel. Note that we've had to synchronize access to the input collection's iterator since it will be called from multiple threads. Also note that this is an unusual use of the iterator, since we never call hasNextand we assume that it is safe to call nextexactly the number of times returned by the input collection's size().

这现在远非单行。我也不清楚它有多大用处。:-) 但它确实证明了可以并行执行这样的操作。请注意,我们必须同步对输入集合迭代器的访问,因为它将从多个线程调用。还要注意,这是迭代器的一种不寻常的使用,因为我们从不调用,hasNext并且我们假设调用next输入集合的size().

回答by Holger

Based on maba's answerthe general solution is:

根据maba 的回答,一般的解决方案是:

collection.stream().forEachOrdered(i -> map.put(i, map.size()));

From the documentation of void forEachOrdered(Consumer<? super T> action):

文档void forEachOrdered(Consumer<? super T> action)

This operation processes the elements one at a time, in encounter order if one exists.

此操作一次处理一个元素,如果存在,则按遇到顺序处理。

The important aspect here that it retains the order if there is one, e.g. if the Collectionis a SortedSetor a List. Such a stream is called an orderedstream (not to confuse with sorted stream). It might invoke the consumer method by different threads but always ensuring the “one at a time” and thread-safety guaranty.

这里的重要方面是,如果有一个,它会保留顺序,例如,如果Collection是 aSortedSet或 a List。这样的流称为有序流(不要与排序流混淆)。它可能会通过不同的线程调用消费者方法,但始终确保“一次一个”和线程安全保证。

Of course, it won't benefit from parallel execution if the stream is parallel.

当然,如果流是并行的,它不会从并行执行中受益。



For completeness, here is the solution which will work even on parallel streams utilizing the parallel processing, if they are still ordered:

为了完整起见,这里的解决方案甚至可以在使用并行处理的并行流上工作,如果它们仍然是有序的

stream.collect(HashMap::new, (m, i) -> m.put(i, m.size()),
  (a, b) -> {int offset = a.size(); b.forEach((k, v) -> a.put(k, v + offset));});

回答by John McClean

If you don't mind using 3rd party libraries, my cyclops-reactlib has extensions for all JDK Collectiontypes, with a large number of powerful operaitons attached, so you could implement this like so :-

如果您不介意使用 3rd 方库,我的cyclops-reactlib 具有适用于所有JDK Collection类型的扩展,并附有大量强大的操作,因此您可以像这样实现:-

    CollectionX<String> col = CollectionX.fromCollection(orgCol);
    col.zipWithIndex()
       .toMap(k->k.v1, v->v.v2);

cyclops-react Collection extensions are eager, so you would get better performance with our Stream extension, ReactiveSeq(which extends jOOλ's Seq, which in turn is an extension of JDK's java.util.stream.Stream, it also implements the reactive-streams api).

cyclops-react 集合扩展是急切的,所以你会用我们的流扩展ReactiveSeq获得更好的性能(它扩展了 jOOλ 的 Seq,而后者又是 JDK 的 java.util.stream.Stream 的扩展,它还实现了 react-streams api )。

    ReactiveSeq.fromCollection(col)
               .zipWithIndex()
               .toMap(k->k.v1, v->v.v2);

回答by K. Gol

You can use AtomicInteger as index in stream:

您可以使用 AtomicInteger 作为流中的索引:


    Collection col = Arrays.asList("a", "b", "c");
    AtomicInteger index = new AtomicInteger(0);
    Map collectedMap =  col.stream().collect(Collectors.toMap(Function.identity(), el -> index.getAndIncrement()));
    System.out.println("collectedMap = " + collectedMap);

回答by Syam S

Try

尝试

    int[] pos = { 0 };
    list.forEach( a -> map.put(a, pos[0]++));