如何在Kotlin中计算排除闭区间后的非相交时间范围?
Kotlin 实现时间区间减法(拆分非相交可用时段)
问题分析
你需要从多个可用时间区间中扣除关闭区间,得到剩余的非相交可用时段——标准库的Range类型仅支持单一连续区间的简单减法,无法处理多区间重叠、跨区间覆盖这类复杂场景,因此需要自定义区间处理逻辑。
实现方案
首先定义时间区间的数据结构,再实现区间相交判断、区间拆分减法的核心逻辑,最后完成多区间的批量扣除计算。
1. 定义时间区间数据类
使用LocalTime表示一天内的时间,封装区间的相交判断、减法拆分和格式化输出:
import java.time.LocalTime import java.time.format.DateTimeFormatter data class TimeRange(val start: LocalTime, val end: LocalTime) { // 判断当前区间与另一区间是否相交 fun overlapsWith(other: TimeRange): Boolean { return start.isBefore(other.end) && end.isAfter(other.start) } // 从当前区间扣除相交的关闭区间,返回剩余的子区间(0-2个) fun subtract(other: TimeRange): List<TimeRange> { val result = mutableListOf<TimeRange>() when { // 关闭区间在当前区间之外,直接保留原区间 other.end <= start || other.start >= end -> result.add(this) // 关闭区间完全覆盖当前区间,无剩余 other.start <= start && other.end >= end -> {} // 关闭区间覆盖当前区间左半部分,保留右侧 other.start <= start && other.end < end -> result.add(TimeRange(other.end, end)) // 关闭区间覆盖当前区间右半部分,保留左侧 other.start > start && other.end >= end -> result.add(TimeRange(start, other.start)) // 关闭区间在当前区间中间,拆分为左右两个子区间 else -> { result.add(TimeRange(start, other.start)) result.add(TimeRange(other.end, end)) } } return result } // 格式化输出为易读字符串(如"7am-10am") fun format(): String { val formatter = DateTimeFormatter.ofPattern("h a") val startStr = start.format(formatter).lowercase().replace(" ", "") val endStr = end.format(formatter).lowercase().replace(" ", "") return "$startStr-$endStr" } }
2. 核心计算函数
对可用区间和关闭区间排序后,依次将每个关闭区间从可用区间中扣除,最终得到剩余的可用时段:
fun calculateAvailableTime(availableRanges: List<TimeRange>, closedRanges: List<TimeRange>): List<TimeRange> { // 按起始时间排序,确保处理顺序正确 val sortedAvailable = availableRanges.sortedBy { it.start } val sortedClosed = closedRanges.sortedBy { it.start } var currentAvailable = sortedAvailable.toMutableList() for (closed in sortedClosed) { val temp = mutableListOf<TimeRange>() for (available in currentAvailable) { if (available.overlapsWith(closed)) { // 拆分并保留剩余区间 temp.addAll(available.subtract(closed)) } else { // 无相交,直接保留原区间 temp.add(available) } } currentAvailable = temp if (currentAvailable.isEmpty()) break // 无可用区间时提前退出 } return currentAvailable.sortedBy { it.start } }
3. 测试场景
fun main() { // 场景1:多个独立关闭区间 val available1 = listOf( TimeRange(LocalTime.of(7, 0), LocalTime.of(12, 0)), TimeRange(LocalTime.of(14, 0), LocalTime.of(22, 0)) ) val closed1 = listOf( TimeRange(LocalTime.of(10, 0), LocalTime.of(11, 0)), TimeRange(LocalTime.of(15, 0), LocalTime.of(17, 0)) ) val result1 = calculateAvailableTime(available1, closed1) println("场景1结果:${result1.joinToString { it.format() }}") // 输出:场景1结果:7am-10am, 11am-12pm, 2pm-3pm, 5pm-10pm // 场景2:跨可用区间的关闭区间 val closed2 = listOf(TimeRange(LocalTime.of(10, 0), LocalTime.of(17, 0))) val result2 = calculateAvailableTime(available1, closed2) println("场景2结果:${result2.joinToString { it.format() }}") // 输出:场景2结果:7am-10am, 5pm-10pm }
为什么标准Range不行?
Kotlin标准库的Range(如LocalTimeRange)是单一连续区间,仅支持简单的区间减法(返回单个区间或空),无法处理拆分出多个子区间、多区间依次扣除这类复杂场景,因此必须自定义区间拆分逻辑。
内容的提问来源于stack exchange,提问作者I.A
相关产品推荐
相关产品推荐

