Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 stream - Merge maps and calculate average of "values"

Let's say I have a List of classes that each has a Map.

public class Test {
    public Map<Long, Integer> map;
}

the Long keys in the Map are timestamps and the Integer values are scores.

I am trying to create a Stream that can combine the maps from all objects and output a Map with the unique Timestamps (The Longs) and an average score.

I have this code, but it gives me the sum of all the scores and not the average (The Integer class doesn't have an average method).

Test test1 = new Test();
    test1.map = new HashMap() {{
        put(1000L, 1);
        put(2000L, 2);
        put(3000L, 3);
    }};

    Test test2 = new Test();
    test2.map = new HashMap() {{
        put(1000L, 10);
        put(2000L, 20);
        put(3000L, 30);
    }};

    List<Test> tests = new ArrayList() {{
        add(test1);
        add(test2);
    }};

    Map<Long, Integer> merged = tests.stream()
            .map(test -> test.map)
            .map(Map::entrySet)
            .flatMap(Collection::stream)
            .collect(
                    Collectors.toMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            Integer::sum

                    )
            );
    System.out.println(merged);

I'm thinking that this might not be an easy problem so solve in a single Stream, so an output with a Map with the unique timestamps and a List of all the scores would also be fine. Then I can calculate the average myself.

Map<Long, List<Integer>> 

It it possible?

like image 633
Martin Avatar asked Nov 30 '17 09:11

Martin


People also ask

How can we get an average of values of elements in a stream operation?

The average() method of the IntStream class in Java returns an OptionalDouble describing the arithmetic mean of elements of this stream, or an empty optional if this stream is empty. It gets the average of the elements of the stream.

What does the merge () method on map do?

The Java HashMap merge() method inserts the specified key/value mapping to the hashmap if the specified key is already not present. If the specified key is already associated with a value, the method replaces the old value with the result of the specified function.

Can we use map stream in Java 8?

Java 8 Stream's map method is intermediate operation and consumes single element forom input Stream and produces single element to output Stream. It simply used to convert Stream of one type to another. Let's see method signature of Stream's map method.


1 Answers

Instead of Collectors.toMap use Collectors.groupingBy:

Map<Long, Double> merged = tests.stream()
        .map(test -> test.map)
        .map(Map::entrySet)
        .flatMap(Collection::stream)
        .collect(
                Collectors.groupingBy(
                        Map.Entry::getKey,
                        Collectors.averagingInt(Map.Entry::getValue)
                )
        );

Oh, and even though you probably don't need it anymore, you can get the Map<Long, List<Integer>> you asked about in the last part of your question just as easily:

Map<Long, List<Integer>> merged = tests.stream()
    .map(test -> test.map)
    .map(Map::entrySet)
    .flatMap(Collection::stream)
    .collect(
            Collectors.groupingBy(
                    Map.Entry::getKey,
                    Collectors.mapping(Map.Entry::getValue, Collectors.toList())
            )
    );
like image 132
Eran Avatar answered Oct 15 '22 09:10

Eran