VitalyT
VitalyT

Reputation: 1701

How make aggregation java8 with specific condition

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

Answers (3)

Ousmane D.
Ousmane D.

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

Garreth Golding
Garreth Golding

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

Ousmane D.
Ousmane D.

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

Related Questions