使用 Java 将 LocalDateTime 对象与 startTIme 和 endTime 分组
Grouping LocalDateTime objects with startTIme and endTime using Java
我想实现一个分组算法以将此列表分组为分钟间隔。
示例列表:
List<Item> items = Arrays.asList(
new Item(LocalDateTime.parse("2020-08-21T00:00:00"), LocalDateTime.parse("2020-08-21T00:02:00"), "item1"),
new Item(LocalDateTime.parse("2020-08-21T00:01:00"), LocalDateTime.parse("2020-08-21T00:03:00"), "item2"),
new Item(LocalDateTime.parse("2020-08-21T00:03:00"), LocalDateTime.parse("2020-08-21T00:07:00"), "item3"),
new Item(LocalDateTime.parse("2020-08-21T00:08:00"), LocalDateTime.parse("2020-08-21T00:12:00"), "item4"),
new Item(LocalDateTime.parse("2020-08-21T09:50:37"), LocalDateTime.parse("2020-08-21T09:56:49"), "item5"),
new Item(LocalDateTime.parse("2020-08-21T09:59:37"), LocalDateTime.parse("2020-08-21T10:02:37"), "item6"),
new Item(LocalDateTime.parse("2020-08-21T09:49:37"), LocalDateTime.parse("2020-08-21T09:51:37"), "item7"),
new Item(LocalDateTime.parse("2019-12-31T23:59:37"), LocalDateTime.parse("2020-01-01T00:03:37"), "item8"),
new Item(LocalDateTime.parse("2020-01-01T00:04:37"), LocalDateTime.parse("2020-01-01T00:06:37"), "item9")
);
项目class:
class Item {
LocalDateTime startTime;
LocalDateTime endTime;
String name;
// constructor etc
}
为简单起见,我将仅提及分钟,但日期也很重要。给定 5 分钟的间隔 00:00 - 00:02
可以分为范围 00:00 - 00:05
的组,而 00:03 - 00:07
可以分为两组 00:00 - 00:05
和 00:05 - 00:10
。
上述示例列表的期望输出(仅用于可读性输出的名称应包含整个 Item 对象):
{
[item1, item2, item3],
[item3, item4],
[item5, item6],
[item7, item5],
[item8, item9]
}
是否可以使用类似 Collectors#groupingBy 的方法进行此类分组?
编辑* 为了避免负面评论,我在答案中添加了我的“非高效”解决方案。
主要问题的简短回答:
Is it possible to do such grouping using a method like Collectors#groupingBy?
是是。
如评论中所述,此任务的主要问题是在一般情况下无法将单个项目“分组”为单个条目,但需要根据两者将其多路复用为多个条目startTime
和 endTime
.
可能可以使用两个以上的5分钟范围,例如:startTime: 00:02; endTime: 00:12
将涵盖三个范围:00:00-00:05
、00:05-00:10
、00:10-00:15
--此案例已更新 item4
。
也就是说,可以提供以下解决方案:
import java.time.*;
import java.util.*;
import java.util.stream.*;
public class Solution {
public static void main(String args[]) {
List<Item> items = Arrays.asList(
new Item(LocalDateTime.parse("2020-08-21T00:00:00"), LocalDateTime.parse("2020-08-21T00:02:00"), "item1"),
new Item(LocalDateTime.parse("2020-08-21T00:01:00"), LocalDateTime.parse("2020-08-21T00:03:00"), "item2"),
new Item(LocalDateTime.parse("2020-08-21T00:03:00"), LocalDateTime.parse("2020-08-21T00:07:00"), "item3"),
new Item(LocalDateTime.parse("2020-08-21T00:04:00"), LocalDateTime.parse("2020-08-21T00:12:00"), "item4"),
new Item(LocalDateTime.parse("2020-08-21T09:50:37"), LocalDateTime.parse("2020-08-21T09:56:49"), "item5"),
new Item(LocalDateTime.parse("2020-08-21T09:59:37"), LocalDateTime.parse("2020-08-21T10:02:37"), "item6"),
new Item(LocalDateTime.parse("2020-08-21T09:49:37"), LocalDateTime.parse("2020-08-21T09:51:37"), "item7"),
new Item(LocalDateTime.parse("2019-12-31T23:59:37"), LocalDateTime.parse("2020-01-01T00:03:37"), "item8"),
new Item(LocalDateTime.parse("2020-01-01T00:04:37"), LocalDateTime.parse("2020-01-01T00:06:37"), "item9"),
// added to test a single entry within 5 min range
new Item(LocalDateTime.parse("2020-01-01T00:42:37"), LocalDateTime.parse("2020-01-01T00:44:37"), "item10")
);
items.stream()
.flatMap(Solution::convert)
.collect(Collectors.groupingBy(x -> x.getKey(), LinkedHashMap::new, Collectors.mapping(x -> x.getValue(), Collectors.toList())))
.values()
.forEach(System.out::println);
}
public static Stream<Map.Entry<LocalDateTime, Item>> convert(Item item) {
LocalDateTime start = getKey(item.getStartTime());
LocalDateTime end = getKey(item.getEndTime()).plusMinutes(5);
return Stream
.iterate(start, d -> d.isBefore(end), d -> d.plusMinutes(5))
.map(d -> Map.entry(d, item));
}
public static LocalDateTime getKey(LocalDateTime time) {
return LocalDateTime.of(time.getYear(), time.getMonthValue(), time.getDayOfMonth(), time.getHour(), time.getMinute() - time.getMinute() % 5);
}
}
输出
[item1, item2, item3, item4]
[item3, item4]
[item4]
[item5, item7]
[item5, item6]
[item6]
[item7]
[item8]
[item8, item9]
[item9]
[item10]
备注
部分Java 9个特征在代码片段中被使用:
更新
Java9个特征可以用下面的Java8个兼容代码代替:
Map.entry -> new AbstractMap.SimpleEntry
- 使用 Java 8
iterate
+ limit(ChronoUnit.MINUTES.between(start, end) / 5)
public static Stream<Map.Entry<String, Item>> convert(Item item) {
LocalDateTime start = getKey(item.getStartTime());
LocalDateTime end = getKey(item.getEndTime()).plusMinutes(5);
return Stream
.iterate(start, d -> d.plusMinutes(5))
.limit(ChronoUnit.MINUTES.between(start, end) / 5)
.map(d -> new AbstractMap.SimpleEntry(d + "**" + d.plusMinutes(5), item));
}
如果筛选出的结果值至少包含两项,则结果如下:
// ...
.entrySet()
.stream()
.filter(x -> x.getValue().size() > 1)
.forEach(System.out::println);
输出
2020-08-21T00:00**2020-08-21T00:05=[item1, item2, item3, item4]
2020-08-21T00:05**2020-08-21T00:10=[item3, item4]
2020-08-21T09:50**2020-08-21T09:55=[item5, item7]
2020-08-21T09:55**2020-08-21T10:00=[item5, item6]
2020-01-01T00:00**2020-01-01T00:05=[item8, item9]
这是我的解决方案:
public static void main(String[] args) {
List<Item> items = Arrays.asList(
new Item(LocalDateTime.parse("2020-08-21T00:00:00"), LocalDateTime.parse("2020-08-21T00:02:00"), "item1"),
new Item(LocalDateTime.parse("2020-08-21T00:01:00"), LocalDateTime.parse("2020-08-21T00:03:00"), "item2"),
new Item(LocalDateTime.parse("2020-08-21T00:03:00"), LocalDateTime.parse("2020-08-21T00:07:00"), "item3"),
new Item(LocalDateTime.parse("2020-08-21T00:08:00"), LocalDateTime.parse("2020-08-21T00:12:00"), "item4"),
new Item(LocalDateTime.parse("2020-08-21T09:50:37"), LocalDateTime.parse("2020-08-21T09:56:49"), "item5"),
new Item(LocalDateTime.parse("2020-08-21T09:59:37"), LocalDateTime.parse("2020-08-21T10:02:37"), "item6"),
new Item(LocalDateTime.parse("2020-08-21T09:49:37"), LocalDateTime.parse("2020-08-21T09:51:37"), "item7"),
new Item(LocalDateTime.parse("2019-12-31T23:59:37"), LocalDateTime.parse("2020-01-01T00:03:37"), "item8"),
new Item(LocalDateTime.parse("2020-01-01T00:04:37"), LocalDateTime.parse("2020-01-01T00:06:37"), "item9")
);
Map<String, List<Item>> groups = new HashMap<>();
items.stream().forEach(item -> {
int startTimeMinute = item.startTime.getMinute();
int startTimeMinutesOver = startTimeMinute % 5;
int endTimeMinute = item.endTime.getMinute();
int endTimeMinutesOver = endTimeMinute % 5;
LocalDateTime firstGroupStartTime = item.startTime.truncatedTo(ChronoUnit.MINUTES).withMinute(startTimeMinute - startTimeMinutesOver);
LocalDateTime secondGroupStartTime = item.endTime.truncatedTo(ChronoUnit.MINUTES).withMinute(endTimeMinute - endTimeMinutesOver);
// check if item belongs to a single or more groups
if (firstGroupStartTime.equals(secondGroupStartTime)) {
String groupRange = firstGroupStartTime.toString() + "**" + firstGroupStartTime.plusMinutes(5).toString();
groups.computeIfAbsent(groupRange, s -> new ArrayList<>()).add(item);
} else {
String firstGroupRange = firstGroupStartTime.toString() + "**" + firstGroupStartTime.plusMinutes(5).toString();
groups.computeIfAbsent(firstGroupRange, s -> new ArrayList<>()).add(item);
String secondGroupRange = secondGroupStartTime.toString() + "**" + secondGroupStartTime.plusMinutes(5).toString();
groups.computeIfAbsent(secondGroupRange, s -> new ArrayList<>()).add(item);
}
});
// remove groups that contain only a single item
groups.entrySet().removeIf(stringListEntry -> stringListEntry.getValue().size() == 1);
for (String key : groups.keySet()) {
System.out.println(String.format("%s %s", key, groups.get(key).stream().map(item -> item.name).collect(Collectors.toList())));
}
}
输出
2020-08-21T00:05**2020-08-21T00:10 [item3, item4]
2020-08-21T00:00**2020-08-21T00:05 [item1, item2, item3]
2020-08-21T09:50**2020-08-21T09:55 [item5, item7]
2020-01-01T00:00**2020-01-01T00:05 [item8, item9]
2020-08-21T09:55**2020-08-21T10:00 [item5, item6]
我最初提出问题的主要原因是要找到一种合适且更有效的方法。考虑到我会有很多组,重复组以删除单个组并不是最好的做法。
我想实现一个分组算法以将此列表分组为分钟间隔。
示例列表:
List<Item> items = Arrays.asList(
new Item(LocalDateTime.parse("2020-08-21T00:00:00"), LocalDateTime.parse("2020-08-21T00:02:00"), "item1"),
new Item(LocalDateTime.parse("2020-08-21T00:01:00"), LocalDateTime.parse("2020-08-21T00:03:00"), "item2"),
new Item(LocalDateTime.parse("2020-08-21T00:03:00"), LocalDateTime.parse("2020-08-21T00:07:00"), "item3"),
new Item(LocalDateTime.parse("2020-08-21T00:08:00"), LocalDateTime.parse("2020-08-21T00:12:00"), "item4"),
new Item(LocalDateTime.parse("2020-08-21T09:50:37"), LocalDateTime.parse("2020-08-21T09:56:49"), "item5"),
new Item(LocalDateTime.parse("2020-08-21T09:59:37"), LocalDateTime.parse("2020-08-21T10:02:37"), "item6"),
new Item(LocalDateTime.parse("2020-08-21T09:49:37"), LocalDateTime.parse("2020-08-21T09:51:37"), "item7"),
new Item(LocalDateTime.parse("2019-12-31T23:59:37"), LocalDateTime.parse("2020-01-01T00:03:37"), "item8"),
new Item(LocalDateTime.parse("2020-01-01T00:04:37"), LocalDateTime.parse("2020-01-01T00:06:37"), "item9")
);
项目class:
class Item {
LocalDateTime startTime;
LocalDateTime endTime;
String name;
// constructor etc
}
为简单起见,我将仅提及分钟,但日期也很重要。给定 5 分钟的间隔 00:00 - 00:02
可以分为范围 00:00 - 00:05
的组,而 00:03 - 00:07
可以分为两组 00:00 - 00:05
和 00:05 - 00:10
。
上述示例列表的期望输出(仅用于可读性输出的名称应包含整个 Item 对象):
{
[item1, item2, item3],
[item3, item4],
[item5, item6],
[item7, item5],
[item8, item9]
}
是否可以使用类似 Collectors#groupingBy 的方法进行此类分组?
编辑* 为了避免负面评论,我在答案中添加了我的“非高效”解决方案。
主要问题的简短回答:
Is it possible to do such grouping using a method like Collectors#groupingBy?
是是。
如评论中所述,此任务的主要问题是在一般情况下无法将单个项目“分组”为单个条目,但需要根据两者将其多路复用为多个条目startTime
和 endTime
.
可能可以使用两个以上的5分钟范围,例如:startTime: 00:02; endTime: 00:12
将涵盖三个范围:00:00-00:05
、00:05-00:10
、00:10-00:15
--此案例已更新 item4
。
也就是说,可以提供以下解决方案:
import java.time.*;
import java.util.*;
import java.util.stream.*;
public class Solution {
public static void main(String args[]) {
List<Item> items = Arrays.asList(
new Item(LocalDateTime.parse("2020-08-21T00:00:00"), LocalDateTime.parse("2020-08-21T00:02:00"), "item1"),
new Item(LocalDateTime.parse("2020-08-21T00:01:00"), LocalDateTime.parse("2020-08-21T00:03:00"), "item2"),
new Item(LocalDateTime.parse("2020-08-21T00:03:00"), LocalDateTime.parse("2020-08-21T00:07:00"), "item3"),
new Item(LocalDateTime.parse("2020-08-21T00:04:00"), LocalDateTime.parse("2020-08-21T00:12:00"), "item4"),
new Item(LocalDateTime.parse("2020-08-21T09:50:37"), LocalDateTime.parse("2020-08-21T09:56:49"), "item5"),
new Item(LocalDateTime.parse("2020-08-21T09:59:37"), LocalDateTime.parse("2020-08-21T10:02:37"), "item6"),
new Item(LocalDateTime.parse("2020-08-21T09:49:37"), LocalDateTime.parse("2020-08-21T09:51:37"), "item7"),
new Item(LocalDateTime.parse("2019-12-31T23:59:37"), LocalDateTime.parse("2020-01-01T00:03:37"), "item8"),
new Item(LocalDateTime.parse("2020-01-01T00:04:37"), LocalDateTime.parse("2020-01-01T00:06:37"), "item9"),
// added to test a single entry within 5 min range
new Item(LocalDateTime.parse("2020-01-01T00:42:37"), LocalDateTime.parse("2020-01-01T00:44:37"), "item10")
);
items.stream()
.flatMap(Solution::convert)
.collect(Collectors.groupingBy(x -> x.getKey(), LinkedHashMap::new, Collectors.mapping(x -> x.getValue(), Collectors.toList())))
.values()
.forEach(System.out::println);
}
public static Stream<Map.Entry<LocalDateTime, Item>> convert(Item item) {
LocalDateTime start = getKey(item.getStartTime());
LocalDateTime end = getKey(item.getEndTime()).plusMinutes(5);
return Stream
.iterate(start, d -> d.isBefore(end), d -> d.plusMinutes(5))
.map(d -> Map.entry(d, item));
}
public static LocalDateTime getKey(LocalDateTime time) {
return LocalDateTime.of(time.getYear(), time.getMonthValue(), time.getDayOfMonth(), time.getHour(), time.getMinute() - time.getMinute() % 5);
}
}
输出
[item1, item2, item3, item4]
[item3, item4]
[item4]
[item5, item7]
[item5, item6]
[item6]
[item7]
[item8]
[item8, item9]
[item9]
[item10]
备注
部分Java 9个特征在代码片段中被使用:
更新
Java9个特征可以用下面的Java8个兼容代码代替:
Map.entry -> new AbstractMap.SimpleEntry
- 使用 Java 8
iterate
+limit(ChronoUnit.MINUTES.between(start, end) / 5)
public static Stream<Map.Entry<String, Item>> convert(Item item) {
LocalDateTime start = getKey(item.getStartTime());
LocalDateTime end = getKey(item.getEndTime()).plusMinutes(5);
return Stream
.iterate(start, d -> d.plusMinutes(5))
.limit(ChronoUnit.MINUTES.between(start, end) / 5)
.map(d -> new AbstractMap.SimpleEntry(d + "**" + d.plusMinutes(5), item));
}
如果筛选出的结果值至少包含两项,则结果如下:
// ...
.entrySet()
.stream()
.filter(x -> x.getValue().size() > 1)
.forEach(System.out::println);
输出
2020-08-21T00:00**2020-08-21T00:05=[item1, item2, item3, item4]
2020-08-21T00:05**2020-08-21T00:10=[item3, item4]
2020-08-21T09:50**2020-08-21T09:55=[item5, item7]
2020-08-21T09:55**2020-08-21T10:00=[item5, item6]
2020-01-01T00:00**2020-01-01T00:05=[item8, item9]
这是我的解决方案:
public static void main(String[] args) {
List<Item> items = Arrays.asList(
new Item(LocalDateTime.parse("2020-08-21T00:00:00"), LocalDateTime.parse("2020-08-21T00:02:00"), "item1"),
new Item(LocalDateTime.parse("2020-08-21T00:01:00"), LocalDateTime.parse("2020-08-21T00:03:00"), "item2"),
new Item(LocalDateTime.parse("2020-08-21T00:03:00"), LocalDateTime.parse("2020-08-21T00:07:00"), "item3"),
new Item(LocalDateTime.parse("2020-08-21T00:08:00"), LocalDateTime.parse("2020-08-21T00:12:00"), "item4"),
new Item(LocalDateTime.parse("2020-08-21T09:50:37"), LocalDateTime.parse("2020-08-21T09:56:49"), "item5"),
new Item(LocalDateTime.parse("2020-08-21T09:59:37"), LocalDateTime.parse("2020-08-21T10:02:37"), "item6"),
new Item(LocalDateTime.parse("2020-08-21T09:49:37"), LocalDateTime.parse("2020-08-21T09:51:37"), "item7"),
new Item(LocalDateTime.parse("2019-12-31T23:59:37"), LocalDateTime.parse("2020-01-01T00:03:37"), "item8"),
new Item(LocalDateTime.parse("2020-01-01T00:04:37"), LocalDateTime.parse("2020-01-01T00:06:37"), "item9")
);
Map<String, List<Item>> groups = new HashMap<>();
items.stream().forEach(item -> {
int startTimeMinute = item.startTime.getMinute();
int startTimeMinutesOver = startTimeMinute % 5;
int endTimeMinute = item.endTime.getMinute();
int endTimeMinutesOver = endTimeMinute % 5;
LocalDateTime firstGroupStartTime = item.startTime.truncatedTo(ChronoUnit.MINUTES).withMinute(startTimeMinute - startTimeMinutesOver);
LocalDateTime secondGroupStartTime = item.endTime.truncatedTo(ChronoUnit.MINUTES).withMinute(endTimeMinute - endTimeMinutesOver);
// check if item belongs to a single or more groups
if (firstGroupStartTime.equals(secondGroupStartTime)) {
String groupRange = firstGroupStartTime.toString() + "**" + firstGroupStartTime.plusMinutes(5).toString();
groups.computeIfAbsent(groupRange, s -> new ArrayList<>()).add(item);
} else {
String firstGroupRange = firstGroupStartTime.toString() + "**" + firstGroupStartTime.plusMinutes(5).toString();
groups.computeIfAbsent(firstGroupRange, s -> new ArrayList<>()).add(item);
String secondGroupRange = secondGroupStartTime.toString() + "**" + secondGroupStartTime.plusMinutes(5).toString();
groups.computeIfAbsent(secondGroupRange, s -> new ArrayList<>()).add(item);
}
});
// remove groups that contain only a single item
groups.entrySet().removeIf(stringListEntry -> stringListEntry.getValue().size() == 1);
for (String key : groups.keySet()) {
System.out.println(String.format("%s %s", key, groups.get(key).stream().map(item -> item.name).collect(Collectors.toList())));
}
}
输出
2020-08-21T00:05**2020-08-21T00:10 [item3, item4]
2020-08-21T00:00**2020-08-21T00:05 [item1, item2, item3]
2020-08-21T09:50**2020-08-21T09:55 [item5, item7]
2020-01-01T00:00**2020-01-01T00:05 [item8, item9]
2020-08-21T09:55**2020-08-21T10:00 [item5, item6]
我最初提出问题的主要原因是要找到一种合适且更有效的方法。考虑到我会有很多组,重复组以删除单个组并不是最好的做法。