Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to group by range of values in Java 8 using streams

Tags:

java

java-8

Here is a sample scenario:

Imagine we have employee records like:

name, age, salary (in 1000 dollars)
   a,  20,     50
   b,  22,     53
   c,  34,     79

and so on. The goal is to calculate the average salary of different age groups (for instance between 21 and 30 and 31 to 40 and so on).

I want to do this using stream and I just cant get my head around how I need to use groupingBy to get this done. I am thinking maybe I need to define some sort of tuple age range. Any ideas?

like image 887
kaptan Avatar asked Nov 05 '15 03:11

kaptan


People also ask

Does Java 8 support streams?

Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.

What does stream of () method in Java?

Stream of(T t) returns a sequential Stream containing a single element. Syntax : static Stream of(T t) Parameters: This method accepts a mandatory parameter t which is the single element in the Stream. Return Value: Stream of(T t) returns a sequential Stream containing the single specified element.

What is the use of groupingBy in Java 8?

The groupingBy() method of Collectors class in Java are used for grouping objects by some property and storing results in a Map instance.

How do I print list values in stream?

There are 3 ways to print the elements of a Stream in Java: forEach() println() with collect() peek()


2 Answers

The below code should give you what you are looking for. The key is "Collectors" class which support grouping.

Map<Double,Integer> ageGroup= employees.stream().collect(Collectors.groupingBy(e->Math.ceil(e.age/10.0),Collectors.summingInt(e->e.salary)));

The illustration assuming the salary is integer but easy to switch to double

The complete program looks like

public static void main(String[] args) {
    // TODO Auto-generated method stub

    List<Employee> employees = new ArrayList<>();
    employees.add(new Employee("a",20,100));
    employees.add(new Employee("a",21,100));
    employees.add(new Employee("a",35,100));
    employees.add(new Employee("a",32,100));


    Map<Double,Integer> ageGroup= employees.stream().collect(Collectors.groupingBy(e->Math.ceil(e.age/10.0),Collectors.summingInt(e->e.salary)));
    System.out.println(ageGroup);
}

public static class Employee {
    public Employee(String name, int age, int salary) {
        super();
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    public String name;
    public int age;
    public int salary;

}

The output is

{4.0=200, 2.0=100, 3.0=100}
like image 166
Baski Avatar answered Sep 22 '22 11:09

Baski


Yes, you could define an AgeGroup interface or even an enum like this (assuming Employee definition):

enum AgeGroup {
    TWENTIES,
    THIRTIES,
    FORTIES,
    FIFTIES;
    .....
}
Function<Employee, AgeGroup> employee2Group = e -> {
    if(e.age >= 20 && e.getAge() < 30)
        return AgeGroup.TWENTIES;
    ....
    return null;
};

Map<AgeGroup, Double> avgByAgeGroup = employees.stream()
    .collect(Collectors.groupingBy(employee2Group, Collectors.averagingInt(Employee::getSalary)));

avgByAgeGroup.get(AgeGroup.TWENTIES)
like image 29
Dave L. Avatar answered Sep 23 '22 11:09

Dave L.