Java 8 lambda:将Collection转换为元素的Map,迭代位置

时间:2014-07-30 15:44:04

标签: java lambda java-8 java-stream

如何将像[" a"," b"," c"]这样的集合转换为类似{" a&#34的地图;: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++);
    }

6 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:6)

这是一种方法:

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

不一定是单行或短于简单循环,但如果您将toMap更改为toConcurrentMap,它确实可以使用并行流。

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

<强>更新

OP已澄清输入为Collection而非List,因此上述内容并不适用。似乎我们对输入Collection的假设很少。 OP已指定迭代顺序。使用顺序迭代器,元素将以某些顺序出现,但不能保证它。它可能会在不同的运行之间发生变化,甚至从一次迭代变为另一次迭代(尽管这在实践中是不常见的 - 除非修改了底层集合)。

如果需要保留确切的迭代顺序,我不相信有将其保留在结果Map中的方法,而不是顺序迭代输入Collection

但是,如果确切的迭代顺序不重要,并且要求输出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));

现在来自单行。我也不清楚它有多有用。 :-)但它确实表明它可以并行地做这样的事情。请注意,我们必须同步访问输入集合的迭代器,因为它将从多个线程调用。另请注意,这是迭代器的一种不寻常的用法,因为我们从不调用hasNext,并且我们假设可以安全地调用next完全由输入集合返回的次数size() { {1}}。

答案 2 :(得分:2)

基于maba’s answer,一般解决方案是:

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

来自documentation of void forEachOrdered(Consumer<? super T> action)

  

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

这里的重要方面是,如果存在订单,它会保留订单,例如如果CollectionSortedSetList。这样的流称为有序流(不要与排序流混淆)。它可能会通过不同的线程调用使用者方法,但始终确保“一次一个”并保证线程安全。

当然,如果流是并行的,它将无法从并行执行中受益。


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

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

答案 3 :(得分:1)

如果您不介意使用第三方库,我的cyclops-react lib具有所有JDK Collection类型的扩展名,附加了大量强大的操作符,因此您可以这样实现: -

length()

cyclops-react集合扩展非常渴望,所以使用我们的Stream扩展ReactiveSeq(它扩展了jOOλ的Seq,后者又是JDK的java.util.stream.Stream的扩展,它会获得更好的性能)也实现了反应流api)。

length()

答案 4 :(得分:1)

您可以在流中使用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);

答案 5 :(得分:0)

尝试

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