как использовать java stream для группировки полей и создания сводки - PullRequest
3 голосов
/ 21 февраля 2020
public class Call {
    private String status;
    private String callName;
}

У меня есть список вызовов, и мне нужно создать сводку, например:

public class CallSummary {
    private String callName;
    private List<ItemSummary> items;
}
public class itemSummary {
    private String status;
    private Integer percentage;
}

Моя цель - показать процент вызовов с некоторым статусом, например:

INBOUND_CALL : {
FAILED = 30%
SUCCESS = 70%
}

как я могу сделать это, используя java 8 поток и коллекторы?

Ответы [ 2 ]

0 голосов
/ 21 февраля 2020

Идея, лежащая в основе группировки, заключается в том, чтобы вкладывать деньги таким образом, чтобы у вас было имя вызова, а затем был доступен поиск по количеству на основе статуса. Я бы также предложил использовать перечисление для статуса

enum CallStatus {
    FAILED, SUCCESS
}

и адаптировать его в других классах как

class Call {
    private CallStatus status;
    private String callName;
}

Затем вы можете реализовать вложенную группировку и начать с промежуточного результата, такого как:

List<Call> sampleCalls = List.of(new Call(CallStatus.SUCCESS,"naman"),new Call(CallStatus.FAILED,"naman"),
        new Call(CallStatus.SUCCESS,"diego"), new Call(CallStatus.FAILED,"diego"), new Call(CallStatus.SUCCESS,"diego"));

Map<String, Map<CallStatus, Long>> groupedMap = sampleCalls.stream()
        .collect(Collectors.groupingBy(Call::getCallName,
                Collectors.groupingBy(Call::getStatus, Collectors.counting())));

, что даст вам вывод

{diego={FAILED=1, SUCCESS=2}, naman={FAILED=1, SUCCESS=1}}

, и вы также можете дополнительно оценить проценты. (хотя представление их в Integer может потерять точность в зависимости от того, как вы оцениваете их дальше.)


Чтобы решить эту проблему дальше, вы можете оставить еще один Map для поиска по количеству на основе имен как:

Map<String, Long> nameBasedCount = calls.stream()
        .collect(Collectors.groupingBy(Call::getCallName, Collectors.counting()));

и далее, вычислять сводки типа CallSummary в List следующим образом:

List<CallSummary> summaries = groupedMap.entrySet().stream()
        .map(entry -> new CallSummary(entry.getKey(), entry.getValue().entrySet()
                .stream()
                .map(en -> new ItemSummary(en.getKey(), percentage(en.getValue(),
                        nameBasedCount.get(entry.getKey()))))
                .collect(Collectors.toList()))
        ).collect(Collectors.toList());

, где percentage count будет реализовано вами с использованием подписи int percentage(long val, long total) также выровнен с типом данных, выбранным в ItemSummary.

Пример результата:

[
CallSummary(callName=diego, items=[ItemSummary(status=FAILED, percentage=33), ItemSummary(status=SUCCESS, percentage=66)]), 
CallSummary(callName=naman, items=[ItemSummary(status=FAILED, percentage=50), ItemSummary(status=SUCCESS, percentage=50)])
]
0 голосов
/ 21 февраля 2020

Следующее собирает в состояние -> процентную карту, которую затем можно преобразовать в выходную модель. Этот код предполагает метод getStatus.

List<Call> calls;
Map<String,Double> statusPercents = calls.stream()
    .collect(Collectors.groupingBy(Call::getStatus,
        Collectors.collectingAndThen(Collectors.counting(), 
            n -> 100.0 * n / calls.size())));

Я понимаю, что этот код немного трудно читать. Цепочка коллекторов группирует вызовы по статусу, а затем подсчитывает каждую группу и, наконец, преобразуется в процент. Вы можете (возможно) сделать его более читабельным, имея временные переменные для сборщиков:

var percentFunction = n -> 100.0 * n / calls.size();
var collectPercent = collectingAndThen(count(), percentFunction);
var collectStatusPercentMap = groupingBy(Call::getStatus, collectPercent);

Вы также хотите сгруппировать по имени вызова, но на самом деле это то же самое - использовать groupingBy, а затем уменьшить список звонков на CallSummary.

...