Reputation: 1701
I'd like to make aggregation of the following example:
I have a List<CsvEntity> toSort =
of entities as described below:
toSort.add(new CsvEntity(...))..
public class CsvEntity {
String OCCURRENCES, STATUS, MESSAGE, STACK_TRACE;
}
The data:
OCCURRENCES, STATUS,MESSAGE,STACK_TRACE
1, FAIL, MESSAGE1, STACK1
1, PASS, MESSAGE1, STACK1
1, FAIL, MESSAGE1, STACK1
1, FAIL, MESSAGE2, STACK2 => aggregate MESSAGE & STACK_TRACE)
1, PASS, MESSAGE2, STACK2
1, PASS, MESSAGE3, STACK3
1, PASS, MESSAGE3, STACK3
the result should be (as data structure):
OCCURRENCES,STATUS,MESSAGE,STACK_TRACE
3, FAIL, MESSAGE1, STACK1
2, FAIL, MESSAGE2, STACK2
2, PASS, MESSAGE3, STACK3
I tried to use:
Map<String, Integer> group = toSort.stream().collect(
Collectors.groupingBy(
CsvEntity::getSTACK_TRACE,
Collectors.groupingBy(CsvEntity::getMESSAGE),
Collectors.summingInt(s -> Integer.parseInt(s.getOCCURRENCES()))
)
);
but this group returns only the STACK_TRACE not the whole CsvEntity...
Is it possible and what to change in the code?
Upvotes: 4
Views: 244
Reputation: 56463
In addition to my other answer, you could use the groupingBy
collector but first I'd override equals
/hashcode
in the CsvEntity
class as follows:
class CsvEntity {
private String OCCURRENCES,STATUS,MESSAGE,STACK_TRACE;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CsvEntity csvEntity = (CsvEntity) o;
return Objects.equals(MESSAGE, csvEntity.MESSAGE) &&
Objects.equals(STACK_TRACE, csvEntity.STACK_TRACE);
}
@Override
public int hashCode() {
return Objects.hash(MESSAGE, STACK_TRACE);
}
public CsvEntity(String OCCURRENCES, String STATUS,
String MESSAGE, String STACK_TRACE) { ... }
...
...
...
}
Then the stream pipeline:
List<CsvEntity> resultSet
= source.stream()
.collect(Collectors.groupingBy(Function.identity(),
LinkedHashMap::new,
Collectors.summingInt(e -> Integer.parseInt(e.getOCCURRENCES()))))
.entrySet()
.stream()
.map(x -> {
CsvEntity c = x.getKey();
return new CsvEntity(Integer.toString(x.getValue()),
c.getSTATUS(), c.getMESSAGE(), c.getSTACK_TRACE());
}).collect(Collectors.toList());
This again yields the following result:
[CsvEntity{OCCURRENCES='3', STATUS='FAIL', MESSAGE='MESSAGE1', STACK_TRACE='STACK1'},
CsvEntity{OCCURRENCES='2', STATUS='FAIL', MESSAGE='MESSAGE2', STACK_TRACE='STACK2'},
CsvEntity{OCCURRENCES='2', STATUS='PASS', MESSAGE='MESSAGE3', STACK_TRACE='STACK3'}]
Upvotes: 2
Reputation: 1005
Below is a sample of how you could group and aggregate the data. Hope this helps.
Code
public static void main(String[] args) {
List<CsvEntity> toSort = getToSort();
Map<String, List<CsvEntity>> grouped = toSort.stream()
.collect(Collectors.groupingBy(o -> o.stackTrace));
List<CsvEntity> aggregated = grouped.entrySet()
.stream()
.map(entry -> {
CsvEntity csvEntity = entry.getValue().get(0);
String occurrences = String.valueOf(entry.getValue().size());
return new CsvEntity(occurrences, csvEntity.status, csvEntity.message, csvEntity.stackTrace);
})
.collect(Collectors.toList());
aggregated.forEach(csvEntity -> System.out.println(csvEntity.toString()));
}
private static List<CsvEntity> getToSort() {
return Arrays.asList(
new CsvEntity("1", "Fail", "Message 1", "Stack 1"),
new CsvEntity("1", "Pass", "Message 1", "Stack 1"),
new CsvEntity("1", "Fail", "Message 1", "Stack 1"),
new CsvEntity("1", "Fail", "Message 2", "Stack 2"),
new CsvEntity("1", "Pass", "Message 2", "Stack 2"),
new CsvEntity("1", "Pass", "Message 3", "Stack 3"),
new CsvEntity("1", "Pass", "Message 3", "Stack 3")
);
}
public static class CsvEntity {
String occurrences;
String status;
String message;
String stackTrace;
CsvEntity(String occurrences, String status, String message, String stackTrace) {
this.occurrences = occurrences;
this.status = status;
this.message = message;
this.stackTrace = stackTrace;
}
@Override
public String toString() {
return occurrences + ", " + status + ", " + message + ", " + stackTrace;
}
}
Output
3, Fail, Message 1, Stack 1
2, Fail, Message 2, Stack 2
2, Pass, Message 3, Stack 3
Upvotes: 0
Reputation: 56463
Here is an example of how to accomplish the aforementioned result:
This uses @Boris the Spider's idea of concatenating the message
and stacktrace
properties for the value to "group by". Although, instead of using the groupingBy
collector it might be better to use the toMap
collector in this specific case.
List<CsvEntity> result = new ArrayList<>(source.stream()
.collect(Collectors.toMap(c -> c.getMESSAGE() + c.getSTACK_TRACE(),
v -> new CsvEntity(v.getOCCURRENCES(), v.getSTATUS(), v.getMESSAGE(), v.getSTACK_TRACE()),
(left, right) -> {
left.setOCCURRENCES(Integer.toString(Integer.parseInt(left.getOCCURRENCES())
+ Integer.parseInt(right.getOCCURRENCES())));
return left;
}, LinkedHashMap::new))
.values());
Note that this solution creates new CsvEntity
objects with the new data but if you want to mutate the objects in the source
list instead then simply change new CsvEntity(v.getOCCURRENCES(), v.getSTATUS(), v.getMESSAGE(), v.getSTACK_TRACE())
to Function.identity()
.
Having executed this code, it yields the following result:
[CsvEntity{OCCURRENCES='3', STATUS='FAIL', MESSAGE='MESSAGE1', STACK_TRACE='STACK1'},
CsvEntity{OCCURRENCES='2', STATUS='FAIL', MESSAGE='MESSAGE2', STACK_TRACE='STACK2'},
CsvEntity{OCCURRENCES='2', STATUS='PASS', MESSAGE='MESSAGE3', STACK_TRACE='STACK3'}]
Upvotes: 0