使用 Java 流和 groupingBy() 从每天的计数中获取每月的计数
Obtain the Count per Month from the Count per Day using Java Streams and groupingBy()
我想将 每天 销售额(随便,无所谓)的列表转换为 每月 的列表] 销售额。
所以我有一个 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()
方法。
这是我的代码:
List<Map<String, Object>> perDay = new ArrayList<>();
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");
Map<String, Object> perMonth = perDay.stream()
.collect(Collectors.groupingBy("???",
Collectors.summingInt(f -> Integer.parseInt((String) f))));
我想,我已经接近了,但我还没有完全做到。
有什么想法吗?
假设您的日期始终采用格式
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"))))));
Returns:
{2022-01=8, 2022-02=4}
您介绍的方法不可维护,因此容易出现错误和代码重复,也使代码的扩展更加困难。
这些是主要问题:
不要将 Object
用作通用类型。因为 Object
类型的集合元素如果不进行转换就一文不值。如果您无意编写充满强制转换和 intanceof
检查的泥泞代码,请不要走这条路。
Map
- 不是 与 JSON-object 相同的东西(我不是在谈论 类 来自 Java-libraries 就像 Gson,它们是 map-based),不要混淆 构建 human-readable 文本的方式 与数据结构.
的实现
像 "DATE"
和 "COUNT"
这样的键是没用的——你可以简单地存储实际的 date 和 count 相反。
使用 String
作为数字和日期的类型不会给您带来任何优势。在不解析的情况下,您无法对它们进行任何操作(除了在控制台上进行比较和打印),几乎与 Object
类似。为每个元素使用适当的数据类型。为什么?因为 LocalDate
、Integer
、YearMonth
具有 String
无法提供给您的独特行为。
这就是您可以通过将表示 每天销售额 List<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)));
这就是您可以从中生成包含 每月销售额 的地图的方法(java.time
包中的 YearMonth
用作 键):
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
首先,我建议不要将地图列表用于日期销售或月份销售。
这是您的数据。
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);
这是您的操作方式。
- 流式传输地图
- 获取日期并仅使用 year/month 部分
- 将计数转换为整数(因此可以求和)
- 当遇到重复键时,添加计数
- 请注意,这涉及值的转换。
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>>
不允许您选择日期,因为地图的 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);
这样也可以更轻松地转换为当月总销售额,如下所示:
- 流式传输
entrySets
perDay
- 转换为
Map<String,Integer>
表示月份和销售额。使用 substring 通过 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 作为值。否则你将不得不进行转换,这可能会导致错误。
考虑使用class来存储相关信息。销售额 class 可用于存储日期和计数以及其他信息。
class Sales {
private int count;
private String date;
// getters
// toString
}
要处理日期,请查看 java.time 包中的 classes。
我想将 每天 销售额(随便,无所谓)的列表转换为 每月 的列表] 销售额。
所以我有一个 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()
方法。
这是我的代码:
List<Map<String, Object>> perDay = new ArrayList<>();
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");
Map<String, Object> perMonth = perDay.stream()
.collect(Collectors.groupingBy("???",
Collectors.summingInt(f -> Integer.parseInt((String) f))));
我想,我已经接近了,但我还没有完全做到。
有什么想法吗?
假设您的日期始终采用格式
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"))))));
Returns:
{2022-01=8, 2022-02=4}
您介绍的方法不可维护,因此容易出现错误和代码重复,也使代码的扩展更加困难。
这些是主要问题:
不要将
Object
用作通用类型。因为Object
类型的集合元素如果不进行转换就一文不值。如果您无意编写充满强制转换和intanceof
检查的泥泞代码,请不要走这条路。
的实现Map
- 不是 与 JSON-object 相同的东西(我不是在谈论 类 来自 Java-libraries 就像 Gson,它们是 map-based),不要混淆 构建 human-readable 文本的方式 与数据结构.像
"DATE"
和"COUNT"
这样的键是没用的——你可以简单地存储实际的 date 和 count 相反。使用
String
作为数字和日期的类型不会给您带来任何优势。在不解析的情况下,您无法对它们进行任何操作(除了在控制台上进行比较和打印),几乎与Object
类似。为每个元素使用适当的数据类型。为什么?因为LocalDate
、Integer
、YearMonth
具有String
无法提供给您的独特行为。
这就是您可以通过将表示 每天销售额 List<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)));
这就是您可以从中生成包含 每月销售额 的地图的方法(java.time
包中的 YearMonth
用作 键):
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
首先,我建议不要将地图列表用于日期销售或月份销售。
这是您的数据。
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);
这是您的操作方式。
- 流式传输地图
- 获取日期并仅使用 year/month 部分
- 将计数转换为整数(因此可以求和)
- 当遇到重复键时,添加计数
- 请注意,这涉及值的转换。
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>>
不允许您选择日期,因为地图的 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);
这样也可以更轻松地转换为当月总销售额,如下所示:
- 流式传输
entrySets
perDay
- 转换为
Map<String,Integer>
表示月份和销售额。使用 substring 通过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 作为值。否则你将不得不进行转换,这可能会导致错误。考虑使用class来存储相关信息。销售额 class 可用于存储日期和计数以及其他信息。
class Sales { private int count; private String date; // getters // toString }
要处理日期,请查看 java.time 包中的 classes。