I'm trying to figure out how to use groupedBy correctly. I have a List with grades, deviations and subjects. Deviation and Grade are returned in the same field and can be differentiated by type
subject | grade_date | grade | type |
---|---|---|---|
Math | 2022-03-01 | 1 | g |
Math | 2022-03-01 | 0.5 | d1 |
Math | 2022-03-01 | 0.5 | d2 |
German | 2022-03-02 | 2 | g |
German | 2022-03-02 | 0.3 | d1 |
German | 2022-03-02 | 0.2 | d2 |
German | 2022-05-01 | 1 | g |
German | 2022-05-01 | 0.5 | d1 |
German | 2022-05-01 | 0.4 | d2 |
My goal is it to group this list by Subject and create a new Object which looks like this.
public class MapObject {
private String subject;
List<Grade> g = new ArrayList<>();
List<Grade> d1 = new ArrayList<>();
List<Grade> d2 = new ArrayList<>();
}
Grade Object would look like this
public class Grade {
private Float grade;
private LocalDate gradeDate;
}
And the result (in JSON) should look like this
[
{
"subject": "Math",
"g": [
{
"grade": 1.0,
"gradeDate": "2022-03-01",
}
],
"d1": [
{
"grade": 0.5,
"gradeDate": "2022-03-01",
}
],
"d2": [
{
"grade": 0.5,
"gradeDate": "2022-03-01",
}
]
},
{
"subject": "German",
"g": [
{
"grade": 2.0,
"gradeDate": "2022-03-02",
},
{
"grade": 1.0,
"gradeDate": "2022-05-01",
}
],
"d1": [
{
"grade": 0.3,
"gradeDate": "2022-03-02",
},
{
"grade": 0.5,
"gradeDate": "2022-05-01",
}
],
"d2": [
{
"grade": 0.2,
"gradeDate": "2022-03-02",
},
{
"grade": 0.4,
"gradeDate": "2022-05-01",
}
]
}
]
I tried to use the groupedBy as follow but I couldn't figure out how to split the lists to g[], d1[], d2[]
var temp = myList.stream()
.collect(Collectors.groupingBy(SourceEntity::getSubject,
Collectors.mapping(s -> {
return new Grade(s.getGrade(), s.getGradeDate());
}, Collectors.toList())))
.entrySet()
.stream()
.map(x -> new MapObject(x.getKey(), x.getValue()))
.collect(Collectors.toList());
CodePudding user response:
As the downstream collector of groupingBy()
you need to provide a collector which associate each type with a list of grades.
For that, you can either use another collector groupingBy()
and pass a combination of mapping()
toList()
as its downstream (as already shown in this answer), or create a custom collector for that purpose.
In order to transform a Map<String,List<Grade>>
(grades by type) into a MapObject
you can introduce a utility method.
List<SourceEntity> myList = // initializing the list
List<MapObject> temp = myList.stream()
.collect(Collectors.groupingBy(
SourceEntity::getSubject,
Collector.of(
HashMap::new,
(Map<String, List<Grade>> gradesByType, SourceEntity next) ->
gradesByType.computeIfAbsent(next.getType(), k -> new ArrayList<>())
.add(new Grade(next.getGrade(), next.gradeDate)),
(left, right) -> {
right.forEach((k, v) -> left.merge(k, v,
(oldV, newV) -> { oldV.addAll(newV); return oldV; }));
return left;
})
))
.entrySet().stream()
.map(entry -> MapObject.of(entry.getKey(), entry.getValue()))
.toList();
public class MapObject {
public static final String GRADE = "g";
public static final String DEVIATION_1 = "d1";
public static final String DEVIATION_2 = "d2";
private String subject;
private List<Grade> g = Collections.emptyList();
private List<Grade> d1 = Collections.emptyList();
private List<Grade> d2 = Collections.emptyList();
// constructors getters
public static MapObject of(String subject, Map<String, List<Grade>> gradesByType) {
return new MapObject(subject,
gradesByType.getOrDefault(GRADE, Collections.emptyList()),
gradesByType.getOrDefault(DEVIATION_1, Collections.emptyList()),
gradesByType.getOrDefault(DEVIATION_2, Collections.emptyList())
);
}
}
CodePudding user response:
You need to groupBy
twice - once by subject, and once by type. This way, the different types are put into a map, and you can easily extract them as lists using get("g")
, get("d1")
, etc.
// Map<String, Map<String, List<Grade>>>
var map = list.stream().collect(Collectors.groupingBy(
x -> x.getSubject(),
Collectors.groupingBy(
x -> x.getType(),
Collectors.mapping(x -> new Grade(x.getGrade(), x.getGradeDate()), Collectors.toList())
)
));
Then you can transform each entry into a MapObject
:
var result = map.entrySet().stream().map(MapObject::fromMapEntry).toList();
where
@Data
@AllArgsConstructor
class Grade {
private Float grade;
private LocalDate gradeDate;
}
@Data
@AllArgsConstructor
class MapObject {
private String subject;
private List<Grade> g;
private List<Grade> d1;
private List<Grade> d2;
private MapObject() {}
public static MapObject fromMapEntry(Map.Entry<String, Map<String, List<Grade>>> entry) {
var mapObject = new MapObject();
mapObject.subject = entry.getKey();
mapObject.g = Objects.requireNonNullElseGet(entry.getValue().get("g"), ArrayList::new);
mapObject.d1 = Objects.requireNonNullElseGet(entry.getValue().get("d1"), ArrayList::new);
mapObject.d2 = Objects.requireNonNullElseGet(entry.getValue().get("d2"), ArrayList::new);
return mapObject;
}
}