如何使用流将元素映射到其索引?

时间:2019-08-24 12:59:00

标签: java java-8 java-stream

我得到了一些自定义对象的流,并且我想创建一个映射Map<Integer, MyObject>,并将每个对象的索引作为键。给你一个简单的例子:

Stream<String> myStream = Arrays.asList("one","two","three").stream();
Integer i = 0;
Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> i++, x -> x));

很显然,它不能编译,因为:

  从Lambda表达式引用的

局部变量必须是final或   有效地最终

是否有一种简单的方法可以将流的元素映射到它们的索引,以便上述示例的预期输出类似于:

{1=one, 2=two, 3=three}

6 个答案:

答案 0 :(得分:11)

您可以使用IntStream来解决此问题:

None

您从List<String> list = Arrays.asList("one","two","three"); Map<Integer, String> map = IntStream.range(0, list.size()).boxed() .collect(Collectors.toMap(Function.identity(), list::get)); IntStream创建一个0IntStream.range()从流中排除最后一个值),并将每个索引映射到列表中的值。此解决方案的优点是,它也可以与并行流一起使用,而使用list.size() - 1则不可能。

因此在这种情况下的结果将是:

AtomicInteger

要在{0=one, 1=two, 2=three} 开始第一个索引,只需在收集过程中添加1

1

这将导致以下结果:

List<String> list = Arrays.asList("one", "two", "three");
Map<Integer, String> map = IntStream.range(0, list.size()).boxed()
        .collect(Collectors.toMap(i -> i + 1, list::get));

答案 1 :(得分:6)

一种不需要随机访问源数据的干净解决方案是

Map<Integer,String> result = Stream.of("one", "two", "three")
    .collect(HashMap::new, (m,s) -> m.put(m.size() + 1, s),
        (m1,m2) -> {
            int offset = m1.size();
            m2.forEach((i,s) -> m1.put(i + offset, s));
        });

这也适用于并行流。

在不太可能的情况下,这是重复执行的任务,值得将逻辑放入可重用的收集器中,包括一些优化:

public static <T> Collector<T,?,Map<Integer,T>> toIndexMap() {
    return Collector.of(
        HashMap::new,
        (m,s) -> m.put(m.size() + 1, s),
        (m1,m2) -> {
            if(m1.isEmpty()) return m2;
            if(!m2.isEmpty()) {
                int offset = m1.size();
                m2.forEach((i,s) -> m1.put(i + offset, s));
            }
            return m1;
        });
}

然后可以像这样使用

Map<Integer,String> result = Stream.of("one", "two", "three")
    .collect(MyCollectors.toIndexMap());

Map<Integer,Integer> result = IntStream.rangeClosed(1, 1000)
    .boxed().parallel()
    .collect(MyCollectors.toIndexMap());

答案 2 :(得分:5)

您的i变量实际上不是最终变量。

您可以将AtomicInteger用作Integer包装器:

Stream<String> myStream = Arrays.asList("one","two","three").stream();
AtomicInteger atomicInteger = new AtomicInteger(0);
Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> atomicInteger.getAndIncrement(), Function.identity()));

我认为这有点棘手,因为它只能解决有效的最终变量问题。由于它是特殊的ThreadSafe版本,因此可能会带来一些开销。 answer by Samuel Philipp中的纯stream解决方案可能更适合您的需求。

答案 3 :(得分:2)

尝试一下:

然后说String[] array = { "V","I","N","A","Y" };

Arrays.stream(array) 
        .map(ele-> index.getAndIncrement() + " -> " + ele) 
        .forEach(System.out::println); 

输出:

0 -> V
1 -> I
2 -> N
3 -> A
4 -> Y

答案 4 :(得分:2)

Guava具有静态方法Streams#mapWithIndex

Stream<String> myStream = Stream.of("one","two","three");
Map<Long, String> result3 = Streams.mapWithIndex(myStream, (s, i) -> Maps.immutableEntry(i + 1, s))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// {1=one, 2=two, 3=three}
System.out.println(result3);

答案 5 :(得分:1)

在构造List.indexOf(Object o)时,我们可以使用Map方法获取列表中元素的索引:

 List<String> list = Arrays.asList("one","two","three");
 Map<Integer, String> result = list.stream()
                                   .collect(Collectors.toMap(
                                    k -> list.indexOf(k) + 1, 
                                    Function.identity(),
                                    (v1, v2) -> v2));

 System.out.println(result);

如果列表中有重复项,则元素的首次出现的索引将添加到最终映射中。同样,要解决键冲突期间地图中的合并错误,我们还需要确保toMap(keyMapper, valueMapper, mergeFunction)

中包含一个合并函数

输出:

{1=one, 2=two, 3=three}