Java 8,整数流,整数流对流的索引进行分组?

时间:2015-01-16 09:10:44

标签: java dictionary java-8 java-stream

我得到了一个Integers流,我想按每个元素的值对元素的索引进行分组。
例如,{1, 1, 1, 2, 3, 3, 4}被分组为整数到索引列表映射:

1 -> 0, 1, 2
2 -> 3
3 -> 4, 5
4 -> 6

我尝试过使用流,但还有一个额外的类:

@Test
public void testGrouping() throws Exception {
    // actually it is being read from a disk file
    Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4);  
    // list to map by index
    int[] ind = {0};  // capture array, effectively final
    class Pair {
        int left;
        int right;

        public Pair(int left, int right) {
            this.left = left;
            this.right = right;
        }
    }

    Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e))
            .collect(Collectors.groupingBy(e -> e.right))
            .entrySet().parallelStream()
            .collect(Collectors.toConcurrentMap(
                    Map.Entry::getKey,
                    e -> e.getValue().parallelStream().map(ee -> ee.left).collect(Collectors.toList())
            ));
}

我必须读取Stream,因为从我的应用程序中的磁盘文件中读取了整数流。
我觉得我这样做的方式非常不理想。是否有更好或更优雅的方式来做到这一点?
谢谢你的帮助。

4 个答案:

答案 0 :(得分:4)

  1. 您可以使用IntStream#range(int startInclusive, int endExclusive)方法获取每个元素的索引。
  2. 然后使用IntStream.boxed()方法将IntStream转换为Stream并加上Integer s
  3. 通过将每个索引映射到数组i -> array[i]中的相应元素并将重复元素收集到列表中来进行分组。
  4. 例如:

    int[] array = {1, 1, 1, 2, 3, 3, 4};
    Map<Integer, List<Integer>> result = 
            IntStream.range(0, array.length)
                     .boxed()
                     .collect(Collectors.groupingBy(i -> array[i], Collectors.toList()));
    

    <强>更新: 如果您没有数组(因此元素数量不足),只有Stream<Integer>,您可以将<{1}}初始Stream的元素收集到{{} 1}}。这样您就可以知道List<Integer>的大小,然后就可以了:

    Stream

答案 1 :(得分:4)

用一个小帮手收集:

class MapAndIndex {
    Map<Integer,List<Integer>> map=new HashMap<>();
    int index;

    void add(int value) {
        map.computeIfAbsent(value, x->new ArrayList<>()).add(index++);
    }
    void merge(MapAndIndex other) {
        other.map.forEach((value,list) -> {
            List<Integer> l=map.computeIfAbsent(value, x->new ArrayList<>());
            for(int i: list) l.add(i+index);
        } );
        index+=other.index;
    }
}

整个操作变为:

Map<Integer,List<Integer>> map = IntStream.of(1, 1, 1, 2, 3, 3, 4)
    .parallel()
    .collect(MapAndIndex::new, MapAndIndex::add, MapAndIndex::merge).map;

当您需要预先跟踪未知的索引时,您需要可变状态,因此需要名为“mutable reduction”的操作。

请注意,此处不需要ConcurrentMapStream实现已经处理了并发性。它将为每个涉及的线程创建一个MapAndIndex容器,并在两个关联线程完成其工作后在两个容器上调用merge操作。如果Stream有一个订单,这也可以保留订单,就像在这个例子中一样(否则你记录索引的任务没有意义......)。

答案 2 :(得分:1)

为什么不:

Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4);  

OfInt indexes = IntStream.iterate(0, x -> x + 1).iterator();
Map<Integer, List<Integer>> result = new HashMap<>();

nums.iterator().forEachRemaining(i -> result.merge(i, 
                                                   new ArrayList<>(Arrays.asList(indexes.next())), 
                                                   (l1, l2) -> {l1.addAll(l2); return l1;})
                                 );

结果:

{1=[0, 1, 2], 2=[3], 3=[4, 5], 4=[6]}

答案 3 :(得分:0)

你能做的是

Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e))
        .collect(groupingBy(p -> p.right, HashMap::new, 
                            mapping(p -> p.left, toList())));

这允许您在元素添加到List之前应用元素的映射。