0

我正在为 String 创建一个扩展,并试图确定下标运算符的正确/预期/良好行为。目前,我有这个:

// Will crash on 0 length strings
subscript(kIndex: Int) -> Character {
    var index = kIndex
    index = index < 0 ? 0 : index
    index = index >= self.length ? self.length-1 : index
    let i = self.startIndex.advancedBy(index)
    return self.characters[i]
}

这会导致字符串范围之外的所有值都被限制在字符串的边缘。虽然这减少了将错误索引传递给下标而导致的崩溃,但它感觉不是正确的做法。BAD_INSTRUCTION如果索引超出范围,我无法从下标引发异常并且不检查下标会导致错误。我能想到的唯一其他选择是返回一个可选的,但这似乎很尴尬。权衡选项,我所拥有的似乎是最合理的,但我认为使用它的任何人都不会期望一个错误的索引返回一个有效的结果。

所以,我的问题是:下标运算符的“标准”预期行为是什么,并且从可接受/适当的无效索引返回有效元素?谢谢。

4

1 回答 1

1

如果您在 上实现下标String,您可能需要首先考虑标准库选择不这样做的原因。

当你打电话时self.startIndex.advancedBy(index),你实际上是在写这样的东西:

var i = self.startIndex
while i < index { i = i.successor() }

这是因为String.CharacterView.Index不是随机访问索引类型。请参阅 上的文档advancedBy。字符串索引不是随机访问的,因为Character字符串中的每个索引都可以是字符串底层存储中的任意数量的字节——你不能像使用 C 字符串那样通过跳转到存储中来获取字符n 。n * characterSize

因此,如果要使用您的下标运算符来遍历字符串中的字符:

for i in 0..<string.characters.count {
    doSomethingWith(string[i])
}

...你会有一个看起来像在线性时间内运行的循环因为它看起来就像一个数组迭代——每次通过循环应该花费相同的时间,因为每个循环都只是递增i并使用一个常量——时间访问得到string[i],对不对?没有。第advancedBy一次通过循环的调用调用successor一次,下一次调用它两次,依此类推...如果您的字符串有n 个字符,则最后一次通过循环调用successor n次(即使生成的结果在前一个在调用successor n-1次时通过循环)。换句话说,你刚刚做了一个 O(n 2) 操作看起来像 O(n) 操作,给使用您的代码的其他人留下了性能成本炸弹。

这是一个完全支持 Unicode 的字符串库的价格。


无论如何,要回答您的实际问题 - 下标和域检查有两种思想流派:

  • 有一个可选的返回类型:func subscript(index: Index) -> Element?

    当客户端无法在不执行与查找相同的工作的情况下检查索引是否有效时,这是有道理的——例如对于字典,找出给定键的值是否与找出给定键的值相同。键的值是。

  • 要求索引有效,否则产生致命错误。

    通常情况下,您的 API 的客户端可以并且应该在访问下标之前检查有效性。这就是 Swift 数组所做的事情,因为数组知道它们的计数,并且您不需要查看数组来查看索引是否有效。

    对此的规范测试是precondition:例如

    func subscript(index: Index) -> Element {
        precondition(isValid(index), "index must be valid")
        // ... do lookup ...
    }
    

    (这里,isValid是一些特定于您的类的用于验证索引的操作——例如,确保它 > 0 并且 < 计数。)

在几乎任何用例中,在索引错误的情况下返回“真实”值都不符合 Swift 的习惯,返回哨兵值也不合适——将带内值与哨兵分开是 Swift 具有 Optionals 的原因。

其中哪一个更适合您的用例是......好吧,因为您的用例存在问题,所以它有点洗牌。如果您precondition的索引 < 计数,您仍然会产生 O(n) 成本来检查它(因为 aString必须检查其内容以确定哪些字节序列构成每个字符,然后才能知道它有多少个字符)。如果您将返回类型设为可选,并在调用advancedByor后返回 nil count,您仍然会产生 O(n) 成本。

于 2016-04-12T19:04:53.873 回答