Использование потоков Java, группирование данных списка на основе интервалов дат и суммирование поля суммы - PullRequest
0 голосов
/ 05 августа 2020

Учтите, что существует класс: Партнер

Class Partner {
 LocalDate invoiceDate;  
 BigDecimal amount;  
}

Теперь у меня есть список объектов-партнеров, отсортированных в порядке убывания, например:

[Partner(invoiceDate=2020-01-21, amount=400), 
 Partner(invoiceDate=2020-01-20, amount=400), 
 Partner(invoiceDate=2020-01-11, amount=400), 
 Partner(invoiceDate=2020-01-10, amount=400),
 .....,
 .....]

В В приведенном выше примере данных поле invoiceDate относится к январю. примечание: в списке будут данные за 12 месяцев или более.

Теперь

  1. Я хочу сгруппировать данные в 15 интервал дней .ie Первый интервал, с 1-го по 15-й день месяца [1–15]. Второй интервал, 16-й день до последнего дня месяца [16 - 30/31/28/29].
  2. И, наконец, суммируйте значение суммы между диапазоном дат.

Расчет по приведенным выше примерным данным: первый интервал [1-15]: 2 строки подходят => [invoiceDate = 2020-01-11 и invoiceDate = 2020-01-10] второй интервал [16-31]: 2 строки квалифицируются => [invoiceDate = 2020-01-21 and invoiceDate = 2020-01-20]

Окончательные выходные данные должны выглядит так:

[Partner(invoiceDate=2020-01-31, amount=800), 
Partner(invoiceDate=2020-01-15, amount=800)]

примечание: в окончательном выводе invoiceDate должен быть последний день интервала.

Ответы [ 4 ]

1 голос
/ 05 августа 2020

Использование groupingBy поможет в такой ситуации. Вот один из подходов:

import static java.util.stream.Collectors.*;
Map<String, BigDecimal> dateRangeToSumMap = list
                    .stream()
                    .collect(
                            groupingBy(e -> e.invoiceDate.getDayOfMonth() > 15 ? "16-31" : "1-16",
                                       mapping(
                                               Partner::getAmount, 
                                               reducing(BigDecimal.ZERO, BigDecimal::add)
                                               )
                                       )
                            );

// iterate the map to get the output
dateRangeToSumMap.forEach((k, v) -> {
                
                System.out.println("Date Range = " + k + " , Sum = " + v);
                
            });

Вывод :

Date Range = 1-16 , Sum = 800
Date Range = 16-31 , Sum = 800

Окончательные выходные данные должны выглядеть так:

[Partner(invoiceDate=2020-01-31, amount=800), 
Partner(invoiceDate=2020-01-15, amount=800)]

Это можно построить с помощью имеющейся у нас карты.

примечание: в окончательном выводе invoiceDate должен быть последний день интервала.

С помощью year и month мы можем получить правильный последний день интервала.

1 голос
/ 05 августа 2020

Вы можете сначала сопоставить invoiceDate с датой с 15-дневным интервалом. Затем используйте Collectors.toMap, чтобы отобразить данные для invoiceDate и суммировать amount в функции слияния, создав новый объект Parter. Наконец, получите значения карты в виде списка.

Map<LocalDate, Partner> result = list.stream()
    .map(p -> new Partner(p.getInvoiceDate().getDayOfMonth() > 15
        ? p.getInvoiceDate().withDayOfMonth(p.getInvoiceDate().lengthOfMonth())
        : p.getInvoiceDate().withDayOfMonth(15),
          p.getAmount()))
    .collect(Collectors.toMap(Partner::getInvoiceDate, e -> e,
        (a, b) -> new Partner(a.getInvoiceDate(), a.getAmount().add(b.getAmount()))));

List<Partner> res = new ArrayList<>(result.values());

Другой подход:

Вы можете упростить код, не создавая объект Partner, как предложил @Naman

static LocalDate getIntervalDate(LocalDate d) {
    return (d.getDayOfMonth() > 15 ? d.withDayOfMonth(d.lengthOfMonth()) 
                                   : d.withDayOfMonth(15));
}

List<Partner> res = list.stream()
    .collect(Collectors.toMap(e -> getIntervalDate(e.getInvoiceDate()), 
                              e -> e.getAmount(), (a, b) -> a.add(b)))
    .entrySet()
    .stream()
    .map(e -> new Partner(e.getKey(), e.getValue()))
    .collect(Collectors.toList());
0 голосов
/ 05 августа 2020
Многословие

Java и отсутствие типа кортежа делает код несколько длиннее, это можно сделать за один проход без какой-либо группировки.

public class Main {
    public static void main(String[] args) {
        List<Partner> partners = Arrays.asList(
                new Partner(LocalDate.of(2020, 1, 21), BigDecimal.valueOf(400)),
                new Partner(LocalDate.of(2020, 1, 20), BigDecimal.valueOf(400)),
                new Partner(LocalDate.of(2020, 1, 11), BigDecimal.valueOf(400)),
                new Partner(LocalDate.of(2020, 1, 10), BigDecimal.valueOf(400))
        );

        Accumulator results = IntStream
                .range(0, partners.size())
                .mapToObj(i -> new AbstractMap.SimpleImmutableEntry<>(i, partners.get(i)))
                .reduce(new Accumulator(), (Accumulator acc, Map.Entry<Integer, Partner> e) -> {
                    Partner p = e.getValue();
                    if (p.invoiceDate.getMonthValue() == acc.month || e.getKey() == 0) {
                        BiWeeklyReport bucket = p.invoiceDate.getDayOfMonth() <= 15 ? acc.first : acc.second;
                        bucket.amount = bucket.amount.add(p.amount);
                        bucket.invoiceDate = bucket.invoiceDate.isAfter(p.invoiceDate) ? bucket.invoiceDate : p.invoiceDate;
                        acc.month = bucket.invoiceDate.getMonthValue();
                    }
                    if (e.getKey() == partners.size() - 1 || p.invoiceDate.getMonthValue() != acc.month) {
                        if (acc.first.amount.compareTo(BigDecimal.ZERO) > 0) {
                            acc.reports.add(acc.first);
                        }
                        if (acc.second.amount.compareTo(BigDecimal.ZERO) > 0) {
                            acc.reports.add(acc.second);
                        }
                        acc.first = new BiWeeklyReport();
                        acc.second = new BiWeeklyReport();
                    }
                    return acc;
                }, (acc1, acc2) -> {
                    acc1.reports.addAll(acc2.reports);
                    return acc1;
                });
        System.out.println(results.reports);
    }

    private static class Partner {
        LocalDate invoiceDate;
        BigDecimal amount;

        private Partner(LocalDate invoiceDate, BigDecimal amount) {
            this.invoiceDate = invoiceDate;
            this.amount = amount;
        }
    }

    private static class BiWeeklyReport {
        BigDecimal amount = BigDecimal.ZERO;
        LocalDate invoiceDate = LocalDate.of(1970, 1, 1);

        @Override
        public String toString() {
            return "BiWeeklyReport{" +
                    "amount=" + amount.longValue() +
                    ", invoiceDate=" + DateTimeFormatter.ISO_LOCAL_DATE.format(invoiceDate) +
                    '}';
        }
    }

    private static class Accumulator {
        List<BiWeeklyReport> reports = new ArrayList<>();
        BiWeeklyReport first = new BiWeeklyReport();
        BiWeeklyReport second = new BiWeeklyReport();
        int month = -1;
    }
}

Вывод:

[BiWeeklyReport{amount=800, invoiceDate=2020-01-11}, BiWeeklyReport{amount=800, invoiceDate=2020-01-21}]
0 голосов
/ 05 августа 2020

Думаю, это должно дать вам хоть что-то для начала:

Function<Partner, LocalDate> getInterval = (partner) -> {
  // compute if day is before 15th of month and return either 15th
  // of month or last day of month
  return null; 
};

Collection<Partner> partners = Arrays.asList();
Map<LocalDate, BigDecimal> result = partners.stream()
    .collect(Collectors.toMap(getInterval, Partner::getAmount, (a1, a2) -> a1.add(a2)));

Вы можете использовать сборщик карт с функцией слияния, чтобы получить сумму для всех элементов с одним и тем же ключом.

...