闭区间合并算法
题目:
给定 N 个闭区间 [begin; end],任意两个相邻或相交的闭区间可以合并为一个闭区间。例如,[1;2] 和 [2;3] 可以合并为 [1;3],[1;3] 和 [2;4] 可以合并为 [1;4],但是[1;2] 和 [3;4] 不可以合并。
目标:
合并这些可以合并的闭区间,求出最终的闭区间集合
代码实现:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* <p>
* IntervalMerging<br>
* 区间合并算法
* </p>
*
* @author XinLau
* @version 1.0
* @since 2021年04月20日 10:45
*/
@Slf4j
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@JsonIgnoreProperties(ignoreUnknown = true)
public class IntervalMerging<T> {
/**
* 区间 - 开始
*/
T begin;
/**
* 区间 - 结束
*/
T end;
/**
* 区间合并算法
*
* @param list - 区间集合
* @param comparator - 比较器
* @param <T> - 数据类型
* @return List<IntervalMerging<T>> - 区间合并
* @author XinLau
* @creed The only constant is change ! ! !
* @since 2021/02/23 10:24
*/
public static <T> List<IntervalMerging<T>> merger(List<IntervalMerging<T>> list, Comparator<T> comparator) {
if (list.isEmpty() || list.size() <= 1) {
// 单区间 直接返回
return list;
}
// 区间集合排序
list.sort((o1, o2) -> comparator.compare(o1.begin, o2.begin));
// 区间合并 返回值
List<IntervalMerging<T>> merger = new ArrayList<>();
// 区间集合
List<IntervalMerging<T>> copy = new ArrayList<>(list);
// 当前区间
IntervalMerging<T> now = list.get(0);
copy.remove(now);
list.forEach(
item -> {
if (comparator.compare(now.begin, item.end) < 0 && comparator.compare(now.end, item.begin) > 0) {
// 交集 now 匹配合并 所有其他的 命中则合并移除
now.begin = comparator.compare(item.begin, now.begin) < 0 ? item.begin : now.begin;
now.end = comparator.compare(item.end, now.end) > 0 ? item.end : now.end;
copy.remove(item);
log.info("merge {} + {} -> {} ", now, item, now);
}
}
);
merger.add(now);
// copy 中剩下的都是跟now无交集的
merger.addAll(merger(copy, comparator));
return merger;
}
}
测试代码:
public static void main(String[] args) {
// 当前人员 的所有 任务 起止时间 记录
List<IntervalMerging<LocalDateTime>> taskTimeArrayList = new ArrayList<>();
// 当前人员 的所有 任务 起止里程 记录
List<IntervalMerging<BigDecimal>> taskMileageArrayList = new ArrayList<>();
// 遍历考勤数据,构建考勤起止时间、起止里程集合
LocalDateTime s = LocalDateTimeUtil.parse("2007-12-03T00:15:30");
LocalDateTime e = LocalDateTimeUtil.parse("2007-12-03T02:15:30");
BigDecimal s1 = new BigDecimal("0.001");
BigDecimal e1 = new BigDecimal("2.001");
for (int i = 0; i < 10; i++) {
if ((i & 1) == 1) {
s = s.plusHours(1);
e = e.plusHours(1);
s1 = s1.add(new BigDecimal("1.00"));
e1 = e1.add(new BigDecimal("1.00"));
} else {
s = s.plusHours(2);
e = e.plusHours(2);
s1 = s1.add(new BigDecimal("2.00"));
e1 = e1.add(new BigDecimal("2.00"));
}
// 任务 起止时间 记录
taskTimeArrayList.add(new IntervalMerging<LocalDateTime>().setBegin(s).setEnd(e));
// 任务 起止里程 记录
taskMileageArrayList.add(new IntervalMerging<BigDecimal>().setBegin(s1).setEnd(e1));
}
// 当前人员 的所有 任务 起止时间 并集 记录
List<IntervalMerging<LocalDateTime>> taskTimeMerger = IntervalMerging.merger(taskTimeArrayList, LocalDateTime::compareTo);
// 当前人员 的所有 任务 起止里程 并集 记录
List<IntervalMerging<BigDecimal>> taskMileageMerger = IntervalMerging.merger(taskMileageArrayList, BigDecimal::compareTo);
// 当前人员 在岗时长(min)
Long duration = taskTimeMerger.stream()
.map(taskTime -> LocalDateTimeUtil.between(taskTime.getBegin(), taskTime.getEnd()))
.mapToLong(Duration::toMillis)
.sum();
System.out.println(duration);
// 当前人员 任务里程(KM)
BigDecimal mileage = taskMileageMerger.stream()
.map(taskMileage -> NumberUtil.sub(taskMileage.getEnd(), taskMileage.getBegin()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(mileage);
}