如何在 NSDate 中添加对 _Incrementable 的一致性

How to add conformance to _Incrementable in NSDate

我正在尝试在 NSDate 中添加对 ForwardIndexType 的一致性,以便我可以制作 Range<NSDate>,为了做到这一点,我必须从 [ 实现 public func successor() -> Self =15=].

我的实现非常简单,旨在说明在另一个日期之后的日期恰好在它之后的一秒,这不是这里要问的。

extension NSDate: ForwardIndexType {
    public func successor() -> Self {
        return NSDate(timeInterval: 1, sinceDate: self) 
    }
}

我得到的错误是

Cannot convert return expression of type 'NSDate' to return type 'Self'

我尝试添加 as Selfas! Self 但编译器不允许我添加,因为在这种情况下从 NSDate 转换为 Self 总是成功。将 Self 替换为 NSDate 也不起作用。

我怎样才能正确地做到这一点?

试试这个,将 Self 更改为 NSDate,删除 ForwardIndexType 协议

    extension NSDate: _Incrementable {
    public func successor() -> Self {
        return self.dynamicType.init(timeInterval: 1, sinceDate: self)
    }
}

在类似代码中使用,

var date = NSDate()
date = date.successor()

let newDate = date.successor()

ForwardIndexType协议中,有advancedBy:distanceTo:等方法,你实现方法successor,只实现协议_Incrementable。这会起作用。

正如一些评论所说,即使可能,您也不应该这样做。 NSDate 没有合乎逻辑的 "next." 发明一个有副作用的风险。 public 类型的扩展是全局的。如果你说 "the next date is the next second" 而另一个扩展说 "the next date is the next day?" 会发生什么?两者同样合理(同样不正确)。切勿添加可能会与其他人的不同含义发生冲突的扩展名。

你说你的目标是:

I want to create a set of n random dates in a given interval. I wanted to shuffle a range and select the first n values

完全没问题。首先,正如您所说,您想要 "in a given interval." 优秀。那是 ClosedInterval<NSDate>。为此,NSDate 必须是 Comparable。添加该扩展名没有错。任何合理实施的人都必须这样实施。

extension NSDate: Comparable {}

public func <(lhs: NSDate, rhs: NSDate) -> Bool {
    return lhs.compare(rhs) == NSComparisonResult.OrderedAscending
}

现在您想将其转换为整秒范围,而不是日期范围。然后打乱该范围内的元素,提取第一个 n 值,并将它们映射回日期。我们假设您已经拥有 Nate Cook 的 shuffle code.

func randomDatesInInterval<DateInterval: IntervalType where DateInterval.Bound == NSDate>
    (interval: DateInterval, count: Int) -> [NSDate] {
    // For convenience we're going to assume that the range is no larger than 68 years.
    // If you need ranges larger than that, it's a bit more work and probably the subject
    // of a second question. (See  for the basis.)
    let intervalSize = UInt32(interval.end.timeIntervalSinceDate(interval.start))

    let offsets = (0...intervalSize).shuffle()

    return Array(offsets.map { interval.start.dateByAddingTimeInterval(NSTimeInterval([=11=])) }.prefix(count))
}

您甚至可以将它与 .....< 一起使用来定义您的间隔:

// 100 second-interval dates from the last hour
randomDatesInInterval(NSDate(timeIntervalSinceNow: -3600)...NSDate(), count: 100)
    .forEach { print([=12=]) }

请注意,如果 n 明显小于间隔中的秒数,则此算法会有点慢并且会占用大量内存。我们必须创建一个可能非常庞大的数字数组,以便按照您的要求进行操作。如果你不关心重复,那一切就简单多了:

let intervalSize = UInt32(interval.end.timeIntervalSinceDate(interval.start))

return (1...count).map { _ in
    let offset = arc4random_uniform(intervalSize)
    return interval.start.dateByAddingTimeInterval(Double(offset))
}

如果间隔明显大于 n,则重复的可能性很低。如果您仍然想避免重复而不必分配那个巨大的初始数组,请考虑 Set:

func randomDatesInInterval<DateInterval: IntervalType where DateInterval.Bound == NSDate>
    (interval: DateInterval, count: Int) -> [NSDate] {
        let intervalSize = UInt32(interval.end.timeIntervalSinceDate(interval.start))

        var offsets = Set<UInt32>()
        while offsets.count < count {
            offsets.insert(arc4random_uniform(intervalSize))
        }

        return offsets.sort().map { interval.start.dateByAddingTimeInterval(NSTimeInterval([=14=])) }
}

a Set 的 trade-off 是如果 n 与间隔中的秒数大小相似,则此方法非常慢。在那种情况下,洗牌效率更高。