如何使用特定条件进行Java8聚合

4

我希望对以下示例进行聚合:

我有一个实体列表List<CsvEntity> toSort =,如下所述:

toSort.add(new CsvEntity(...))..

public class CsvEntity {
    String OCCURRENCES, STATUS, MESSAGE, STACK_TRACE;
}

数据:

  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

结果应该是(作为数据结构):
OCCURRENCES,STATUS,MESSAGE,STACK_TRACE
3, FAIL, MESSAGE1, STACK1
2, FAIL, MESSAGE2, STACK2
2, PASS, MESSAGE3, STACK3

我尝试使用:

Map<String, Integer> group = toSort.stream().collect(
    Collectors.groupingBy(
        CsvEntity::getSTACK_TRACE, 
        Collectors.groupingBy(CsvEntity::getMESSAGE),
        Collectors.summingInt(s -> Integer.parseInt(s.getOCCURRENCES()))
    )
);

但是这个组仅返回 STACK_TRACE 而不是整个 CsvEntity...。是否可能改变代码,让其返回整个 CsvEntity?

1
CsvEntity的成员应该是常量吗?如果不是,请不要使用大写字母来命名它们。 - user9455968
1
当然可以。1)定义正确的分组运算符 - 您似乎想按消息和堆栈进行分组 - 因此请按它们连接进行分组。2)定义一个适当的下游收集器,存储消息和堆栈,并具有您可以增加的计数器,并且可能对状态执行逻辑与操作。 - Boris the Spider
你能举个例子吗? :) - VitalyT
为什么要使用 public class CsvEntity 而不是 public enum CsvEntity - Victor Gubin
对于这个逻辑/问题,无论是驼峰式还是不是都没有关系。假设类的成员。 - VitalyT
显示剩余3条评论
3个回答

2
除了我之前的回答,您还可以使用groupingBy收集器,但首先我会在CsvEntity类中重写equals/hashcode方法,如下所示:
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) { ... }
     ...
     ...
     ...
}

接下来是流水线:

 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());

这会产生以下结果:
[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'}]

1
哇,这个解决方案真是优雅啊,我会很快检查它的 :) ,谢谢。 - VitalyT

0
以下是如何对数据进行分组和聚合的示例。希望这能帮到你。 代码
 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;
        }
    }

输出

3, Fail, Message 1, Stack 1
2, Fail, Message 2, Stack 2
2, Pass, Message 3, Stack 3

嗨@VitalyT..我误读了问题。我已更新我的代码,只在stackTrace上进行分组。 - Garreth Golding

0
以下是如何实现上述结果的示例:
这里使用了@Boris the Spider提出的将messagestacktrace属性连接起来作为“分组依据”的思路。虽然,在这种特定情况下,可能更好地使用toMap收集器而不是groupingBy收集器。
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());

请注意,此解决方案使用新数据创建新的CsvEntity对象,但如果您想要改变source列表中的对象,则只需将新的CsvEntity(v.getOCCURRENCES(), v.getSTATUS(), v.getMESSAGE(), v.getSTACK_TRACE())更改为Function.identity()
执行此代码后,它会产生以下结果:
[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'}]

我需要实现这两个属性的哈希码/相等性吗? 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(getMESSAGE(), csvEntity.getMESSAGE()) && Objects.equals(getSTACK_TRACE(), csvEntity.getSTACK_TRACE()); }@Override public int hashCode() { return Objects.hash(getMESSAGE(), getSTACK_TRACE()); } - VitalyT
@VitalyT 如果你想的话可以这样做,但是在当前的解决方案中不需要。在当前的解决方案中,如果两个对象具有相同的“message”和“stacktrace”,则它们是相等的,因此为c.getMESSAGE() + c.getSTACK_TRACE() - Ousmane D.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接