Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8, Stream of Integer, Grouping indexes of a stream by the Integers?

I got a stream of Integers, and I would like to group the indexes of the elements by each element's value.
For example, {1, 1, 1, 2, 3, 3, 4} is grouped as Integer to list of indexes mapping:

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

I have tried using stream, but with an additional class:

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

I have to read Stream since the Stream of Integer is read from a disk file in my application.
I feel my way of doing it as above is pretty sub-optimal. Is there is a better or more elegant way to do it?
Thanks for your help.

like image 503
Daniel Avatar asked Jan 16 '15 09:01

Daniel


People also ask

How do I find the index of a stream?

Create an AtomicInteger for index. Get the Stream from the array using Arrays. stream() method. Map each elements of the stream with an index associated with it using map() method where the index is fetched from the AtomicInteger by auto-incrementing index everytime with the help of getAndIncrement() method.

What is the use of groupingBy in Java 8?

groupingBy() method in Java 8 now permits developers to perform GROUP BY operation directly. GROUP BY is a SQL aggregate operation that is quite useful. It enables you to categorise records based on specified criteria.

What are two types of streams in Java 8?

With Java 8, Collection interface has two methods to generate a Stream. stream() − Returns a sequential stream considering collection as its source. parallelStream() − Returns a parallel Stream considering collection as its source.

Which stream operations are needed to get sum of all even numbers from stream of Integer values?

Using Stream#sum With String.


2 Answers

With a little helper method for collecting:

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

the entire operation becomes:

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

When you need to track the indices which are unknown beforehand, you need mutable state and hence the operation called “mutable reduction”.

Note that you don’t need a ConcurrentMap here. The Stream implementation will already handle the concurrency. It will create one MapAndIndex container for each involved thread and invoke the merge operation on two containers once both associated threads are done with their work. This will also done in a way retaining the order, if the Stream has an order, like in this example (otherwise your task of recording indices makes no sense…).

like image 101
Holger Avatar answered Nov 15 '22 18:11

Holger


  1. You can use the IntStream#range(int startInclusive, int endExclusive) method to get the index of each element.
  2. Then use the IntStream.boxed() method to convert the IntStream to a Stream with boxed Integers
  3. Group by mapping each index to the corresponding element from the array i -> array[i] and collecting the repeating elements into a list.

For example:

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

Update: If you don't have the array (and therefore the elements count), but a Stream<Integer>, you can collect the elements of the initial Stream into a List<Integer>. This way you will know the size of the Stream and then you can do:

Stream<Integer> = .... // The input stream goes here
//Collecting the input stream to a list, so that we get it's size.
List<Integer> list = stream.collect(Collectors.toList());
//Grouping process
Map<Integer, List<Integer>> result = 
    IntStream.range(0, list.size())
             .boxed()
             .collect(Collectors.groupingBy(i -> list.get(i), Collectors.toList()));
like image 32
Konstantin Yovkov Avatar answered Nov 15 '22 20:11

Konstantin Yovkov