我想实现一种分组算法,以将此列表分为分钟间隔。
示例列表:
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 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
。 / p>
上述示例列表的所需输出(仅出于可读性输出而省略的名称应包含整个Item对象):
{
[item1, item2, item3],
[item3, item4],
[item5, item6],
[item7, item5],
[item8, item9]
}
是否可以使用Collectors#groupingBy之类的方法进行分组?
编辑 *为了避免出现负面评论,我在答案中添加了“无效”解决方案。
答案 0 :(得分:3)
主要问题的简短答案:
是否可以使用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功能:
更新
可以用以下Java 8兼容代码替换Java 9功能:
Map.entry -> new AbstractMap.SimpleEntry
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]
答案 1 :(得分:1)
这是我的解决方案:
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]
我提出最初问题的主要原因是找到一种适当且更有效的方法。考虑到我会有很多小组,重申小组的讨论以删除单个小组并不是最好的选择。