使用Java Streams和groupingBy()从每天计数获取每月计数

2ic8powd  于 2023-04-10  发布在  Java
关注(0)|答案(3)|浏览(113)

我想将一个包含 per-day 销售额(无论什么,都无所谓)的列表转换为一个包含 per-month 销售额的列表。
所以我有一个List<Map<String, Object>>perDay,它包含这些每日值:

[{DATE=2022-01-01, COUNT=5}, {DATE=2022-01-02, COUNT=3}, {DATE=2022-02-29, COUNT=4}]

这些值应该被收集到每月的值中,如下所示:

[{DATE=2022-01, COUNT=8}, {DATE=2022-02, COUNT=4}]

我想使用groupingBy()方法处理流。
下面是我的代码:

Map<String, Object> jan1 = new HashMap<>();
    jan1.put("DATE", "2022-01-01"); jan1.put("COUNT", "5");
    Map<String, Object> jan2 = new HashMap<>();
    jan2.put("DATE", "2022-01-02"); jan2.put("COUNT", "3");
    Map<String, Object> feb1 = new HashMap<>();
    feb1.put("DATE", "2022-02-29"); feb1.put("COUNT", "4");
    List<Map<String, Object>> perDay = List.of(jan1, jan2, feb1);

Map<String, Object> perMonth = perDay.stream()
        .collect(Collectors.groupingBy("???", 
                    Collectors.summingInt(f -> Integer.parseInt((String) f))));

我想,我快了,但还没到那一步。
有什么想法吗

mbyulnm0

mbyulnm01#

假设你的约会总是有格式的

2022-01-01

您可以尝试:

var map = perDay.stream().collect(
                  Collectors.groupingBy(monthMap -> ((String) monthMap.get("DATE")).substring(0, 7),
                  Collectors.summingInt(monthMap -> Integer.parseInt(String.valueOf(monthMap.get("COUNT"))))));

退货:

{2022-01=8, 2022-02=4}
aiazj4mn

aiazj4mn2#

您所介绍的方法是不可维护的,因此容易出现错误和代码重复,并且也使代码的扩展更加困难。
这些是主要问题:

  • 不要使用Object作为泛型类型。因为Object类型的集合元素如果没有强制转换就没有任何价值。如果你不想写充满强制转换和intanceof检查的混乱代码,就不要这样做。
  • Map-不是与JSON对象相同的东西(* 我不是在谈论像Gson这样的Java库中的类,它们是基于Map的 ),不要混淆 * 构建人类可读文本的方式 * 与数据结构的实现*。
  • "DATE""COUNT"这样的键是无用的-您可以简单地存储实际的日期计数
  • 使用String作为数字和日期的类型不会给您带来任何好处。您无法对它们做任何事情(* 除了在控制台上比较和打印 *)没有解析,几乎就像Object一样。为每个元素使用适当的数据类型。为什么?因为LocalDateIntegerYearMonth有自己独特的行为,这是String无法提供的。

这就是如何通过将表示 sales per dayList<Map<String, Object>>的集合调优为更方便的Map<LocalDate, Integer>来改进代码:

Map<LocalDate, Integer> salesPerDay = perDay.stream()
    .map(map -> Map.entry(LocalDate.parse((String) map.get("DATE"), DateTimeFormatter.ISO_DATE),
                          Integer.parseInt((String) map.get("COUNT"))))
    .collect(Collectors.groupingBy(
                Map.Entry::getKey,
                Collectors.summingInt(Map.Entry::getValue)));

这就是如何生成一个包含 sales per month 的Map(java.time包中的YearMonth用作 key):

Map<YearMonth, Integer> salesPerMonth = salesPerDay.entrySet().stream()
    .collect(Collectors.groupingBy(
                entry -> YearMonth.from(entry.getKey()),
                Collectors.summingInt(Map.Entry::getValue)));

salesPerMonth.forEach((k, v) -> System.out.println(k + " -> " + v));
  • 输出:*
2022-01 -> 8
2022-02 -> 4

A link to Online Demo

pbossiut

pbossiut3#

首先,我建议不要使用Map列表来进行日期销售或月份销售。
这是你的数据。

Map<String, Object> jan1 = new HashMap<>();
jan1.put("DATE", "2022-01-01");
jan1.put("COUNT", "5");
Map<String, Object> jan2 = new HashMap<>();
jan2.put("DATE", "2022-01-02");
jan2.put("COUNT", "3");
Map<String, Object> feb1 = new HashMap<>();
feb1.put("DATE", "2022-02-29");
feb1.put("COUNT", "4");
List<Map<String, Object>> perDay = List.of(jan1, jan2, feb1);

这就是你要做的。

  • 流式传输Map
  • 取日期并仅使用年/月部分
  • 将计数转换为整数(以便求和)
  • 当遇到重复的键时,将计数相加
  • 注意,这涉及值的转换。
Map<String, Integer> map = perDay.stream()
        .collect(Collectors.toMap(
                mp -> ((String) mp.get("DATE")).substring(0,
                        7),
                mp -> Integer
                        .valueOf((String) mp.get("COUNT")),
                Integer::sum));

但是你的初始List<Map<String,Object>>不允许你选择日期,因为Map的DATE值与列表的索引没有关系。但是,如果你将它们存储为Map<String,Integer>,那么你就可以使用yyyy-MM-dd的一致格式来检索任何DATE的计数。这个过程不需要强制转换。

Map<String, Integer> perDay = new HashMap<>();
perDay.put("2022-01-01", 5);
perDay.put("2022-01-02", 3);
perDay.put("2022-02-29", 4);

这也使得它更容易转换为月总销售额如下:

  • perDay流式传输entrySets
  • 将月份和销售额转换为Map<String,Integer>。使用子字符串通过Entry.getKey()仅获取年份和月份部分。注意:只有当你的日期格式是一致的时,这才有效。
  • 使用方法引用从Entry::getValue获取计数。
  • 对于重复的计数(同一个月,不同天),使用Integer::sum对它们求和
Map<String, Integer> monthSales = daySales.entrySet()
        .stream().collect(Collectors.toMap(e->e.getKey().substring(0,7),
                             Entry::getValue,
                             Integer::sum));

monthSales.entrySet().forEach(System.out::println);

印刷品

2022-01=8
2022-02=4

和以前一样,您可以获得任何yyyy-MM键的每月计数(如果存在)。
建议:

  • 首先,不要使用Object。使用一个特定的类型,在你的例子中,Integer作为值。否则你将不得不进行类型转换,这会导致错误。
  • 考虑使用一个类来存储相关的信息。一个sales类可以用来存储日期和计数以及其他信息。
class Sales {
     private int count; 
     private String date;
     // getters
     // toString
}
  • 要处理日期,请查看java.time包中的类。

相关问题