Reputation: 2731
I have a problem about writing a java stream by filtering multiple conditions , groupby multiple condition (if possible) and calculating the sum value
I store the values as Map<String(pId),List<Person>>
Here is my Person class shown below
public class Person{
private String id;
private Statement event; // STATUS1,STATUS2
private LocalDate eventDate;
private Object value;
}
Here is the list
ID,Info,Date,Value (All values are stored in Person object defined in the List)
per1, STATUS1, 10-01-2022, 1
per2, STATUS2, 10-01-2022, 2
per3, STATUS3, 10-01-2022, 3
per1, STATUS4, 10-01-2022, 1
per1 STATUS1, 10-02-2022, 1
per2, STATUS1, 10-03-2022, 1
per3, STATUS2, 10-03-2022, 2
...
...
What I want to do is to get this result.
Month | Total Sum | Person Count
1 7 3
2 1 1
3 3 2
Here is group dto shown below
public class GroupDto {
private int month;
private String id;
}
Here is my dto shown below.
public class DTO {
private int month;
private BigDecimal totalSum;
private int totalPersons;
}
Here is the code snippet but I cannot handle with it.
List<Dto> group = employees.values().stream()
.flatMap(List::stream)
.filter(emp -> emp.getEvent() == Value.STATUS1 || emp.getEvent() == Value.STATUS2
|| emp.getEvent() == Value.STATUS3 || emp.getEvent() == Value.STATUS4)
.map(emp -> new Dto(emp.getEventDate().getMonthValue(), new BigDecimal(emp.getValue().toString()), 1))
.collect(Collectors.toList());
List<Dto> result = new ArrayList<>(group.stream()
.collect(Collectors.toMap(
dto -> Arrays.asList(dto.getMonth()), Function.identity(), Dto::aggregate))
.values()).stream().sorted(Comparator.comparing(Dto::getMonth))
.collect(Collectors.toList());
public static Dto aggregate(Dto initial, Dto next) {
initial.setTotalSalary(initial.getTotalSalary().add(next.getTotalSalary()));
initial.setTotalEmployees(initial.getTotalEmployees() + next.getTotalEmployees());
return initial;
}
result.forEach(System.out::println);
Here is the same result
Dto{month=1, totalSalary=7, totalPersons=4}
Dto{month=2, totalSalary=1, totalPersons=1}
Dto{month=3, totalSalary=3, totalPersons=2}
Normally, total persons in month 1 is 3 but it counts 4 with respect to status.
My Issue : I cannot count the person, It counts by status. How can I do that?
Upvotes: 0
Views: 1004
Reputation: 28255
Create a statistics class that models your results. I'm going to use Java records introduced in JDK 14 to make the examples concise.
First we introduce your model class for Person and your enum. Note that I've changed the value
to an int type to make the example simpler.
enum Statement {
STATUS1, STATUS2, STATUS3
}
record Person(String id,
Statement event,
LocalDate eventDate,
int value) {}
We will now create a PersonGroupMetric
that you will use to capture the metrics that you require, it will take a Person as a constructor to allow for easy mapping and has an add
method that we will use as a combiner later when we provide the implementation for collecting your stream.
record PersonGroupMetric(int count, int sum) {
public static final PersonGroupMetric EMPTY = new PersonGroupMetric(0, 0);
public PersonGroupMetric(Person p) {
this(1, p.value());
}
public PersonGroupMetric add(PersonGroupMetric other) {
return new PersonGroupMetric(
this.count + other.count,
this.sum + other.sum
);
}
}
Given your initial dataset:
var src = List.of(
new Person("per1", Statement.STATUS1, LocalDate.of(2022, 01, 10), 1),
new Person("per2", Statement.STATUS2, LocalDate.of(2022, 01, 10), 2),
new Person("per3", Statement.STATUS3, LocalDate.of(2022, 01, 10), 3),
new Person("per1", Statement.STATUS1, LocalDate.of(2022, 02, 10), 1),
new Person("per2", Statement.STATUS1, LocalDate.of(2022, 03, 10), 1),
new Person("per3", Statement.STATUS2, LocalDate.of(2022, 03, 10), 2)
);
We can now use a groupingBy collector that uses a reducer built from your metric model class:
Map<Integer, PersonGroupMetric> res = src.stream()
.collect(groupingBy(
p -> p.eventDate().getMonthValue(),
reducing(
PersonGroupMetric.EMPTY,
PersonGroupMetric::new,
PersonGroupMetric::add
)
));
This now contains a map that groups by the month with the value containing your metric class.
You can now map this to your domain object DTO
from the maps entrySet()
:
var fin = res.entrySet().stream()
.map(n -> new DTO(
n.getKey(),
n.getValue().sum(),
n.getValue().count()
))
.collect(toList());
Imports required:
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.reducing;
import static java.util.stream.Collectors.toList;
Upvotes: 1