如何实现日历中计划安排的排序

102 阅读3分钟

目录

本文收录在《如何开发一个自己的笔记软件》系列中,该系列源码均可在 Blossom 笔记软件 仓库中查看。仓库地址:

如何实现日历中计划安排的排序

在日历中,我们常常看到在一个日历中添加多个计划时,每条计划会占据单独的一行,并且每行之间不会重叠,而且每一行并不是顺序排列的,而是穿插在一起的,例如下图

日历计划排序.png

cc.gif

那么这种排序是如何实现的呢?

首先在新增时,一条计划的数据跨度为几天,那么数据就为几条,例如如下数据,相同的groupId代表同一条计划

[{
    "id":1,
	"groupId": 1,
    "date": "2023-09-01"
}, {
    "id":2,
	"groupId": 1, // 注意,该条与第一条是统一条计划
    "date": "2023-09-02"
}, {
	"id":3,
	"groupId": 2,
	"date": "2023-09-02" // 注意,与第二条是同一天的不同计划
}]

那么在 Java 中表示该数据即为:

// 保存着计划的集合
List<PlanDay> plans

然后将数据转换为按天分组的数据

private static TreeMap<Date, List<PlanDayRes>> byDay(List<PlanDayRes> plans) {
  TreeMap<Date, List<PlanDayRes>> map = new TreeMap<>();
  for (PlanDayRes plan : plans) {
      List<PlanDayRes> list = map.getOrDefault(plan.getPlanDate(), new ArrayList<>());
      list.add(plan);
      map.put(plan.getPlanDate(), list);
  }
}

再将数据转换为按groupId分组的数据,注意,尽管是两个 map,但是所保存的对象引用都是相同的。也就是说,一个 map 中的数据发生变更,另一个 map 中的数据也会发生变更

private static TreeMap<Long, List<PlanDayRes>> byGroupId(List<PlanDayRes> plans) {
    TreeMap<Long, List<PlanDayRes>> map = new TreeMap<>();
    for (PlanDayRes plan : plans) {
        List<PlanDayRes> list = map.getOrDefault(plan.getGroupId(), new ArrayList<>());
        list.add(plan);
        map.put(plan.getGroupId(), list);
    }
    return map;
}

拥有了上述的两个分组后,我们正式进行排序,首先对第一天的数据进行排序,也就是遍历 byDay 方法获取的 map。

第一天开始遍历时,所有的数据都是未进行排序的。此时可以把第一天的第一条计划的顺序设置为1

计划排序.png

相应的,我们要把与他是同一条计划,但不同天的数据的排序都设置为1,也就是说当分组中的一条决定了排序后,改组所有数据都使用该排序。并且此时第二条数据也确认排在了第一天的顺序2。如下图:

PRINTSCREEN_20230907_230410.png##shadow##w500

此时如果第二天又有了新的计划,由于第二天的排序1已经被占用了,所以第二天的第二条计划也就只能位于排序2了,如下图绿色计划:

PRINTSCREEN_20230907_231902.png##shadow##w570

此时可以看到,第三天的排序1是空着的,那么当遍历第三天的数据时,如果有绿色计划之外的其他安排,就可以放置在排序1了。以上就是全部思路了,其实逻辑很简单,下面是排序代码:

public static List<PlanDayRes> sort(List<PlanDayRes> plans) {
    if (CollUtil.isEmpty(plans)) {
        return plans;
    }
    Map<Date, List<PlanDayRes>> byDay = byDay(plans);
    Map<Long, List<PlanDayRes>> byGroupId = byGroupId(plans);

    // 先为所有计划设置默认排序
    plans.forEach(p -> p.setSort(-1));

    byDay.forEach((date, list) -> {
        for (PlanDayRes plan : list) {
            if (plan.getSort() == null) {
                plan.setSort(-1);
            }
            // 排序如果不为 -1,相当于该计划已经被安排好排序了,则跳过
            if (plan.getSort() > -1) {
                continue;
            }
            // 遍历当天中所有的计划
            for (int i = 0; i < list.size(); i++) {
                // 从当天的 排序1 开始
                final int targetSort = i;
              	// 如果该排序已经被占用,则跳过
                boolean targetSortHasUsed = list.stream().anyMatch(p -> p.getSort() == targetSort && p.getId() > 0);
                if (targetSortHasUsed) {
                    continue;
                }
                // 否则将该计划以及计划所在组的所有计划都设置为该排序
                for (PlanDayRes groupPlan : byGroupId.get(plan.getGroupId())) {
                    groupPlan.setSort(targetSort);
                }
            }
        }

        // 这是将空的位置设置一个占位的对象,这样方便遍历,例如上图中,第三天的绿色计划排序为2,但排序1的位置是没有计划的。
        // 占位对象就是填充在了这个位置,方便遍历。
        for (int i = 0; i < list.stream().mapToInt(PlanDayRes::getSort).max().getAsInt(); i++) {
            final int targetSort = i;
            if (list.stream().noneMatch(p -> p.getSort() == targetSort)) {
                plans.add(getHolderPlan(date, targetSort));
            }
        }
    });
    return plans;
}

我还写了一个调试工具,可以查看排序后的结果,例如数据如下:

public static void main(String[] args) {
    List<PlanDayRes> plans = new ArrayList<>();
    PlanDayRes p1_1 = new PlanDayRes();
    p1_1.setId(1L);
    p1_1.setGroupId(1L);
    p1_1.setPlanDate(DateUtils.parse("2023-09-01", DateUtils.PATTERN_YYYYMMDD));
    plans.add(p1_1);

    PlanDayRes p1_2 = new PlanDayRes();
    p1_2.setId(2L);
    p1_2.setGroupId(1L);
    p1_2.setPlanDate(DateUtils.parse("2023-09-02", DateUtils.PATTERN_YYYYMMDD));
    plans.add(p1_2);

    PlanDayRes p2 = new PlanDayRes();
    p2.setId(3L);
    p2.setGroupId(2L);
    p2.setPlanDate(DateUtils.parse("2023-09-01", DateUtils.PATTERN_YYYYMMDD));
    plans.add(p2);

    PlanDayRes p3_1 = new PlanDayRes();
    p3_1.setId(4L);
    p3_1.setGroupId(3L);
    p3_1.setPlanDate(DateUtils.parse("2023-09-02", DateUtils.PATTERN_YYYYMMDD));
    plans.add(p3_1);

    PlanDayRes p3_2 = new PlanDayRes();
    p3_2.setId(4L);
    p3_2.setGroupId(3L);
    p3_2.setPlanDate(DateUtils.parse("2023-09-03", DateUtils.PATTERN_YYYYMMDD));
    plans.add(p3_2);

    sortToTreeMap(plans, true);
}

输出结果如下,日期下的数字为 groupId,可以看到确实如预想的进行了排序

2023-09-01|2023-09-02|2023-09-03|
         2|         3|         3|
         1|         1|          |
          |          |          |

本文的全部源码,以及页面样式和前端代码可在如下地址查看,源码所在项目是一个作者维护的双链笔记软件,也希望大家能给个 star