简体   繁体   English

使用 Java 流根据日期间隔对列表数据进行分组并对金额字段求和

[英]Using Java streams grouping the list data based on the date intervals and sum the amount field

Consider there is a class: Partner考虑有一个 class:合作伙伴

Class Partner {
 LocalDate invoiceDate;  
 BigDecimal amount;  
}

Now, I have a list of Partner objects sorted in descending order eg:现在,我有一个按降序排序的合作伙伴对象列表,例如:

[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),
 .....,
 .....]

In the above sample data the field "invoiceDate" is for January month.在上面的示例数据中,“invoiceDate”字段是一月份的。
note: the list will have data for 12 months or above.注意:该列表将包含 12 个月或以上的数据。

Now,现在,

  1. I want to group the data in 15 days interval.ie我想以 15 天的间隔对数据进行分组。即
    First interval, 1st day to 15th day of the month [1 - 15].第一个间隔,每月的第 1 天到第 15 天 [1 - 15]。
    Second interval, 16th day to Last day of the month [16 - 30/31/28/29].第二个间隔,第 16 天到该月的最后一天 [16 - 30/31/28/29]。
  2. And finally sum the amount value between the date range.最后总结日期范围之间的金额值。

Calculation form the above sample data:由上述样本数据计算:
first interval [1-15]: 2 rows qualifies => [invoiceDate=2020-01-11 and invoiceDate=2020-01-10]第一个间隔 [1-15]: 2 行符合条件 => [invoiceDate=2020-01-11 和 invoiceDate=2020-01-10]
second interval [16-31]: 2 rows qualifies => [invoiceDate=2020-01-21 and invoiceDate=2020-01-20]第二个间隔 [16-31]: 2 行符合条件 => [invoiceDate=2020-01-21 和 invoiceDate=2020-01-20]

The final output data should look like this:最终的 output 数据应如下所示:

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

note: In the final output invoiceDate should be the last day of the interval.注意:在最后的 output invoiceDate 应该是间隔的最后一天。

Using groupingBy would help in such a situation.在这种情况下,使用groupingBy会有所帮助。 Here is one of the approaches:这是其中一种方法:

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

Output : Output

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

The final output data should look like this:最终的 output 数据应如下所示:

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

This can be constructed with the map we have.这可以使用我们拥有的 map 构建。

note: In the final output invoiceDate should be the last day of the interval.注意:在最后的 output invoiceDate 应该是间隔的最后一天。

With year and month , we can get the correct last day of the interval.使用yearmonth ,我们可以得到正确的间隔的最后一天。

You can first map invoiceDate into 15-day interval date.您可以先将 map invoiceDate改成 15 天间隔日期。 Then use Collectors.toMap to map the data into for invoiceDate and sum the amount in merge function by creating new Parter object.然后使用Collectors.toMap到 map 将数据放入invoiceDate并通过创建新的Parter object 将合并 function 中的amount相加。 Finally get the map values as list.最后得到 map 值作为列表。

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

Another approach:另一种方法:

You can simplified the code without creating the Partner object the way @Naman suggested您可以简化代码,而无需按照@Naman 建议的方式创建合作伙伴 object

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

I think this should give you at least something to start with:我认为这应该至少给你一些开始:

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

You can use the map collector with the merge function to get the sum for all elements with the same key.您可以使用 map 收集器和合并 function 来获取具有相同键的所有元素的总和。

Java's verbosity and lack of a tuple type makes the code somewhat longer, this can be done in one pass without any grouping. 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;
    }
}

Output: Output:

[BiWeeklyReport{amount=800, invoiceDate=2020-01-11}, BiWeeklyReport{amount=800, invoiceDate=2020-01-21}]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM