符合String.CharacterView.Index符合Strideable:使用stride时出现致命错误(to:by :):"无法增加endIndex"

时间:2016-03-24 16:32:25

标签: swift

问题:

尝试跨越String.CharacterView.Index指数时,例如大步2

extension String.CharacterView.Index : Strideable { }

let str = "01234"
for _ in str.startIndex.stride(to: str.endIndex, by: 2) { } // fatal error

我得到以下运行时异常

  

致命错误:无法递增endIndex

仅创建上面的StrideTo<String.CharacterView.Index>,(let foo = str.startIndex.stride(to: str.endIndex, by: 2))不会产生错误,只有在尝试跨步/迭代或对其进行操作时才会产生错误(.next()?)。< / p>

  • 此运行时异常的原因是什么;是否预期(误用Stridable)?

我使用 Swift 2.2 Xcode 7.3 。详情如下。

编辑添加:错误来源

在仔细阅读我的问题后,似乎错误确实发生在next()的{​​{1}}方法中(参见本文底部),特别是在以下标记的行

StrideToGenerator

即使永远不会返回let ret = current current += stride // <-- here return ret 的最后一次更新(在下一次调用current中),next()索引的最终前进值也会大于或等于current。 {1}}产生上面的特定运行时错误(对于_end类型Index)。

String.CharacterView.Index

然而,仍然存在一个问题:

  • 这是(0..<4).startIndex.advancedBy(4) // OK, -> 4 "foo".startIndex.advancedBy(4) // fatal error: cannot increment endIndex next()方法中的错误,还是由于StrideToGenerator String.CharacterView.Index符合Stridable而导致的错误?< / LI>

相关

以下Q&amp; A与+1 以外的步骤中迭代字符的主题相关,并且即使这两个问题不同,也值得包含在此问题中。

特别注意上面帖子中的@Sulthan:s neat solution

详细

(对我自己的详细信息/调查表示道歉,如果您可以在没有详细说明的情况下回答我的问题,请跳过这些部分)

String.CharacterView.Index type描述了一个角色位置,并且:

  • 符合Comparable(以及Equatable),
  • 包含advancedBy(_:)distanceTo(_:)的实现。

因此,可以直接使其符合protocol Strideable,使用Stridable:方法stride(through:by:)stride(to:by:)的默认实现。下面的例子将集中讨论后者(与前者类似的问题):

  

...

func stride(to end: Self, by stride: Self.Stride) -> StrideTo<Self>
     

返回值的值序列(self,self + stride,self + stride +   stride,... last)其中last是进展中的最后一个值   小于结束

遵守Stridable并迈出1:一切顺利

String.CharacterView.Index扩展为Stridable并大步前进1可以正常工作:

extension String.CharacterView.Index : Strideable { }

var str = "0123"

// stride by 1: all good
str.startIndex.stride(to: str.endIndex, by: 1).forEach {
    print($0,str.characters[$0])
} /* 0 0 
     1 1 
     2 2
     3 3 */

对于str以上(索引0..<4)中的偶数索引,这也适用于2的步幅:

// stride by 2: OK for even number of characters in str.
str.startIndex.stride(to: str.endIndex, by: 2).forEach {
    print($0,str.characters[$0])
} /* 0 0
     2 2 */

但是,对于某些跨越>1的情况:运行时异常

但是,对于奇数个索引和步长2,字符视图索引的步幅会产生运行时错误

// stride by 2: fatal error for odd number of characters in str.
str = "01234"
str.startIndex.stride(to: str.endIndex, by: 2).forEach {
    print($0,str.characters[$0])
} /* 0 0
     2 2
     fatal error: cannot increment endIndex */

调查我自己的

我对此的调查让我怀疑错误来自StrideToGenerator structurenext()方法,可能是此方法在stridable元素上调用+=

public func += <T : Strideable>(inout lhs: T, rhs: T.Stride) {
  lhs = lhs.advancedBy(rhs)
}

(来自swift/stdlib/public/core/Stride.swift的Swift源版本,有点对应Swift 2.2)。鉴于以下Q&amp; A:s

我们可能会怀疑我们可能需要使用上面的String.CharacterView.Index.advancedBy(_:limit:)而不是...advancedBy(_:)但是从我所看到的情况来看,next()中的StrideToGenerator方法可以防止索引超过限制。

编辑添加:错误的来源似乎确实位于next()中的StrideToGenerator方法中:

// ... in StrideToGenerator
public mutating func next() -> Element? {
    if stride > 0 ? current >= end : current <= end {
        return nil
    }
    let ret = current
    current += stride /* <-- will increase current to larger or equal to end 
                               if stride is large enough (even if this last current
                               will never be returned in next call to next()) */
    return ret
}

即使永远不会返回current的最后一次更新(在下一次调用next()中),current索引的最终前进值也会大于或等于end。对于Index类型String.CharacterView.Index,{1}}会产生上面的特定运行时错误。

(0..<4).startIndex.advancedBy(4) // OK, -> 4
"foo".startIndex.advancedBy(4)   // fatal error: cannot increment endIndex

这是否被视为错误,或String.CharacterView.Index根本不打算(直接)符合Stridable

3 个答案:

答案 0 :(得分:4)

简单地声明协议一致性

extension String.CharacterView.Index : Strideable { }

编译,因为String.CharacterView.Index符合 BidirectionalIndexTypeForwardIndexType/BidirectionalIndexType具有advancedBy()distanceTo()的默认方法实施 根据{{​​1}}的要求。

Strideable具有默认的协议方法实现 Strideable

stride()

所以“直接”实现的唯一方法 extension Strideable { // ... public func stride(to end: Self, by stride: Self.Stride) -> StrideTo<Self> } - 就我所知 - 来自String.CharacterView.Index的{​​{1}}和successor()方法。

正如您已经想到的那样,默认方法实现了 predecessor()BidirectionalIndexType无效。

但总是可以为具体类型定义专用方法。对于使stride()符合String.CharacterView.Index问题,请参阅 下面的Vatsal Manot's answer以及评论中的讨论 - 我花了一段时间才得到他的意思:)

以下是String.CharacterView.Index的{​​{1}}方法的可能实现:

Strideable

这似乎按预期工作:

stride(to:by:)

输出

String.CharacterView.Index

答案 1 :(得分:2)

简单回答你的结束问题:这不是一个错误。这是正常行为。

String.CharacterView.Index永远不会超过父构造的endIndex(即字符视图),因此在被强制时会触发运行时错误(正如在答案的后半部分中正确指出的那样)。这是设计的。

唯一的解决方案是编写自己的stride(to:by:)替代方案,避免以任何方式等同或超过endIndex

如您所知,您可以在技术上实施Strideable,但无法阻止该错误。由于stride(to:by:)不是在协议本身内进行蓝图,而是在扩展中引入,因此您无法使用&#34;自定义&#34;通用范围内的stride(to:by:)(即<T: Strideable>等)。这意味着您可能不应该尝试实现它,除非您完全确定不会发生错误;似乎不可能的事情。

解决方案:目前还没有。但是,如果您认为这是一个问题,我建议您在swift-evolution mailing list中启动一个主题,最好能够收到此主题。

答案 2 :(得分:0)

这不是一个真正的答案;只是你的问题让我玩弄了。让我们忽略Stridable并尝试跨越角色视图:

    let str = "01234"
    var i = str.startIndex
    // i = i.advancedBy(1)
    let inc = 2
    while true {
        print(str.characters[i])
        if i.distanceTo(str.endIndex) > inc {
            i = i.advancedBy(inc)
        } else {
            break
        }
    }

正如您所看到的,在之前调用distanceTo时,使用advancedBy 进行测试至关重要。否则,我们冒险尝试通过最终索引前进,我们将得到“致命错误:无法增加endIndex”炸弹。

所以我的想法是,为了使角色视图的索引具有可争议性,必须有这样的东西。