从 DateInterval 数组中排除 DateInterval 数组

exclude Array of DateInterval from Array of DateInterval

我想从 DateInterval 数组中排除 DateInterval 数组。这是我的代码,但我认为它不会有帮助..它有时会进入无限循环,我无法解决它。

extension DateInterval {

    static func exclude(_ excludedIntervals: [DateInterval], from periods: [DateInterval]) -> [DateInterval] {
        if excludedIntervals.isEmpty { return periods }
        var resultSlots: [DateInterval] = []

        for period in periods {
            let results = period.exclude(excludedIntervals)
            resultSlots.append(contentsOf: results)
        }

        return resultSlots
    }

    func exclude(_ execludedIntervals: [DateInterval]) -> [DateInterval] {
        if execludedIntervals.isEmpty { return [self] }
        var sortedExecludedIntervals = execludedIntervals.sorted()
        var resultSlots: [DateInterval] = []
        var execludedInterval = sortedExecludedIntervals.removeFirst()
        // remove execludedIntervals from self
        if let intersection = self.intersection(with: execludedInterval) {
            if self.start == intersection.start && self.end > intersection.end {
                let newSlot = DateInterval(start: intersection.end, end: self.end)
                resultSlots.append(contentsOf: newSlot.exclude(sortedExecludedIntervals))
            } else if self.start < intersection.start && self.end == intersection.end {
                let newSlot = DateInterval(start: self.start, end: intersection.start)
                resultSlots.append(contentsOf: newSlot.exclude(sortedExecludedIntervals))
            } else if self.start < intersection.start && self.end > intersection.end {
                let preSlot = DateInterval(start: self.start, end: intersection.start)
                resultSlots.append(contentsOf: preSlot.exclude(sortedExecludedIntervals))
                let postSlot = DateInterval(start: intersection.end, end: self.end)
                resultSlots.append(contentsOf: postSlot.exclude(sortedExecludedIntervals))
            } else {
                // start = start && end = end
                resultSlots = []
                return resultSlots
            }
        }


        return resultSlots
    } 
}

例如,我想从中午 12 点到下午 6 点的时间间隔中排除下午 1 点到下午 3 点和下午 5 点到下午 6 点的时间间隔。该函数应 return 中午 12 点 - 下午 1 点和下午 3 点 - 下午 5 点。

几个想法:

  1. 如果我想要一个方法对 DateInterval 的数组进行操作,我建议将其放在 Array(或 Sequence)扩展中,限于 DateInterval 类型,而不是 DateInterval:

    上的 static 方法
    extension Array where Element == DateInterval {
        func exclude(_ excludedIntervals: [DateInterval]) -> [DateInterval] { ... }
    }
    
  2. 当考虑从另一个中排除一个 DateInterval 时,有很多不同的场景:

    • 您可以从间隔中间排除一些小的 window;
    • 您可以在间隔开始时排除一部分;
    • 您可以排除间隔末尾的一部分;和
    • 您可以排除整个区间。

    在我看来,考虑所有这些场景太混乱了,所以我决定稍微简化一下并决定:

    • 排除区域与当前区间相交的确切位置(DateInterval 为我们提供了一个很好的方法);
    • 如果我从日期间隔中“剪掉”那个交叉点,我可能会得到两个间隔,一个 before 间隔和一个 after 间隔(例如,如果我剪掉 2pm-3pm中午至下午 6 点,before 间隔时间为中午至下午 2 点,after 间隔时间为下午 3 点至下午 6 点);
    • 算法然后提炼为“如果区间与排除区域相交,则将原始区间替换为另外两个区间,一个在前,一个在后”;和
    • 考虑到我正在改变结果区间的原始数组,我建议使用嵌套循环,外循环是要排除的区间,内循环是结果区间,因为它在变异,我将使用 while 语句进行迭代,手动检查和调整当前的 index

产生:

extension Array where Element == DateInterval {
    func exclude(_ excludedIntervals: [DateInterval]) -> [DateInterval] {
        var results: [DateInterval] = self

        for excludedInterval in excludedIntervals {
            var index = results.startIndex
            while index < results.endIndex {
                let interval = results[index]
                if let intersection = interval.intersection(with: excludedInterval) {
                    var before: DateInterval?
                    var after: DateInterval?

                    if intersection.start > interval.start {
                        before = DateInterval(start: interval.start, end: intersection.start)
                    }
                    if intersection.end < interval.end {
                        after = DateInterval(start: intersection.end, end: interval.end)
                    }
                    let replacements = [before, after].compactMap { [=11=] }
                    results.replaceSubrange(index...index, with: replacements)
                    index += replacements.count
                } else {
                    index += 1
                }
            }
        }

        return results
    }
}

然后我们可以将应用于单个 DateInterval 的排除视为仅包含一项的数组的特例:

extension DateInterval {
    func exclude(_ excludedIntervals: [DateInterval]) -> [DateInterval] {
        return [self].exclude(excludedIntervals)
    }
}

所以:

let formatter = ISO8601DateFormatter()

let start = formatter.date(from: "2019-02-09T12:00:00Z")!
let end = formatter.date(from: "2019-02-09T18:00:00Z")!

let exclude1Start = formatter.date(from: "2019-02-09T13:00:00Z")!
let exclude1End = formatter.date(from: "2019-02-09T14:00:00Z")!

let exclude2Start = formatter.date(from: "2019-02-09T16:00:00Z")!
let exclude2End = formatter.date(from: "2019-02-09T17:00:00Z")!

let intervals = DateInterval(start: start, end: end)
    .exclude([
        DateInterval(start: exclude1Start, end: exclude1End),
        DateInterval(start: exclude2Start, end: exclude2End)
    ])

print(intervals)

将产生:

[
    2019-02-09 12:00:00 +0000 to 2019-02-09 13:00:00 +0000,
    2019-02-09 14:00:00 +0000 to 2019-02-09 16:00:00 +0000,
    2019-02-09 17:00:00 +0000 to 2019-02-09 18:00:00 +0000
]