使用Java使用startTIme和endTime对LocalDateTime对象进行分组

时间:2020-08-24 11:51:24

标签: java java-8 java-time

我想实现一种分组算法,以将此列表分为分钟间隔。

示例列表:

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:0500:05 - 00:10。 / p>

上述示例列表的所需输出(仅出于可读性输出而省略的名称应包含整个Item对象):

{
    [item1, item2, item3],
    [item3, item4],
    [item5, item6],
    [item7, item5],
    [item8, item9]
}

是否可以使用Collectors#groupingBy之类的方法进行分组?

编辑 *为了避免出现负面评论,我在答案中添加了“无效”解决方案。

2 个答案:

答案 0 :(得分:3)

主要问题的简短答案:

是否可以使用Collectors#groupingBy之类的方法进行此类分组?

如评论中所述,此任务的主要问题是一般情况下不能将单个项目“分组”为单个条目,但是需要根据startTime和{ endTime

可能会使用两个以上的5分钟范围,例如:startTime: 00:02; endTime: 00:12将覆盖三个范围:00:00-00:0500:05-00:1000: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
  • 使用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]

Online demo

答案 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]

我提出最初问题的主要原因是找到一种适当且更有效的方法。考虑到我会有很多小组,重申小组的讨论以删除单个小组并不是最好的选择。