String.Index如何在Swift中工作

时间:2016-09-24 13:36:47

标签: swift string indexing

我一直在使用Swift 3更新我的一些旧代码和答案,但是当我使用Swift Strings和Indexing时,理解事情一直很痛苦。

具体来说,我正在尝试以下方法:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5) // error

第二行给出了以下错误

  

'advancedBy'不可用:要将索引推进n步,请在生成索引的CharacterView实例上调用'index(_:offsetBy :)'。

我看到String有以下方法。

str.index(after: String.Index)
str.index(before: String.Index)
str.index(String.Index, offsetBy: String.IndexDistance)
str.index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

起初这些让我很困惑所以我开始玩它们直到我理解它们为止。我在下面添加一个答案来说明它们是如何使用的。

5 个答案:

答案 0 :(得分:215)

enter image description here

以下所有示例均使用

var str = "Hello, playground"

startIndexendIndex

  • startIndex是第一个字符的索引
  • endIndex最后一个字符的索引。

实施例

// character
str[str.startIndex] // H
str[str.endIndex]   // error: after last character

// range
let range = str.startIndex..<str.endIndex
str[range]  // "Hello, playground"

使用Swift 4&#39; one-sided ranges,范围可以简化为以下形式之一。

let range = str.startIndex...
let range = ..<str.endIndex

为了清楚起见,我将使用以下示例中的完整表单,但为了便于阅读,您可能希望在代码中使用单侧范围。

after

如:index(after: String.Index)

  • after指的是直接在给定索引之后的字符索引。

实施例

// character
let index = str.index(after: str.startIndex)
str[index]  // "e"

// range
let range = str.index(after: str.startIndex)..<str.endIndex
str[range]  // "ello, playground"

before

如:index(before: String.Index)

  • before指的是直接在给定索引之前的字符索引。

实施例

// character
let index = str.index(before: str.endIndex)
str[index]  // d

// range
let range = str.startIndex..<str.index(before: str.endIndex)
str[range]  // Hello, playgroun

offsetBy

如:index(String.Index, offsetBy: String.IndexDistance)

  • offsetBy值可以是正数或负数,并从给定的索引开始。虽然它属于String.IndexDistance类型,但您可以为其指定Int

实施例

// character
let index = str.index(str.startIndex, offsetBy: 7)
str[index]  // p

// range
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
str[range]  // play

limitedBy

如:index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • limitedBy对于确保偏移量不会导致索引超出范围非常有用。这是一个有界的指数。由于偏移量可能超出限制,因此此方法返回Optional。如果索引超出范围,则返回nil

实施例

// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
    str[index]  // p
}

如果偏移量为77而不是7,则会跳过if语句。

为什么需要String.Index?

对于字符串,使用Int索引会更容易 。你必须为每个String创建一个新的String.Index的原因是Swift中的字符长度不同。单个Swift字符可能由一个,两个甚至更多Unicode代码点组成。因此,每个唯一的String必须计算其Character的索引。

可能会隐藏Int索引扩展后面的这种复杂性,但我不愿意这样做。提醒实际发生的事情是件好事。

答案 1 :(得分:1)

我感谢这个问题以及所有相关信息。我想到的是关于String.Index的一个问题和一个答案。

我正在尝试查看是否有O(1)方式访问字符串内的子字符串(或字符),因为如果您查看string.index(startIndex,offsetBy:1)的速度为O(n),索引函数的定义。当然我们可以做类似的事情:

let characterArray = Array(string)

然后访问characterArray中的任何位置,但是它的空间复杂度为n =字符串的长度O(n),因此有点浪费空间。

我一直在看Xcode中的Swift.String文档,并且有一个冻结的公共结构Index。我们可以初始化为:

let index = String.Index(encodedOffset: 0)

然后只需访问或打印String对象中的任何索引,如下所示:

print(string[index])

注意:请注意不要越界`

这很有效,但是这样做的运行时间和空间复杂度是多少?好吗?

答案 2 :(得分:0)

在tableViewController内部创建一个UITextView。我使用了函数:textViewDidChange,然后检查了返回键输入。 然后如果检测到回车键输入,则删除回车键的输入并关闭键盘。

func textViewDidChange(_ textView: UITextView) {
    tableView.beginUpdates()
    if textView.text.contains("\n"){
        textView.text.remove(at: textView.text.index(before: textView.text.endIndex))
        textView.resignFirstResponder()
    }
    tableView.endUpdates()
}

答案 3 :(得分:-1)

func change(string: inout String) {

    var character: Character = .normal

    enum Character {
        case space
        case newLine
        case normal
    }

    for i in stride(from: string.count - 1, through: 0, by: -1) {
        // first get index
        let index: String.Index?
        if i != 0 {
            index = string.index(after: string.index(string.startIndex, offsetBy: i - 1))
        } else {
            index = string.startIndex
        }

        if string[index!] == "\n" {

            if character != .normal {

                if character == .newLine {
                    string.remove(at: index!)
                } else if character == .space {
                    let number = string.index(after: string.index(string.startIndex, offsetBy: i))
                    if string[number] == " " {
                        string.remove(at: number)
                    }
                    character = .newLine
                }

            } else {
                character = .newLine
            }

        } else if string[index!] == " " {

            if character != .normal {

                string.remove(at: index!)

            } else {
                character = .space
            }

        } else {

            character = .normal

        }

    }

    // startIndex
    guard string.count > 0 else { return }
    if string[string.startIndex] == "\n" || string[string.startIndex] == " " {
        string.remove(at: string.startIndex)
    }

    // endIndex - here is a little more complicated!
    guard string.count > 0 else { return }
    let index = string.index(before: string.endIndex)
    if string[index] == "\n" || string[index] == " " {
        string.remove(at: index)
    }

}

答案 4 :(得分:-2)

Swift 4完整解决方案:

OffsetIndexableCollection(使用Int索引的字符串)

https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-