在 Swift 中 String. Index 是如何工作的

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

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

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

第二行给我的错误是什么

“ AdvancedBy”不可用: 要按 n 个步骤提前索引,请在生成索引的 CharterView 实例上调用“ 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)

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

143768 次浏览

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的 单边范围,范围可以简化为以下形式之一。

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有助于确保偏移量不会导致索引超出界限。它是一个边界指数。由于偏移量可能超过限制,因此此方法返回一个。如果索引超出界限,则返回 nil

例子

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

如果偏移量是 77而不是 7,那么就会跳过 if语句。

为什么需要 String. Index?

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

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

在 tableViewController 内部创建一个 UITextView。 然后如果检测到返回键输入,删除返回键输入,关闭键盘。

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()
}
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)
}


}

我很感激这个问题和所有的信息。说到 String 我脑子里有个问题和答案。索引。

我试图看看是否有一个 O (1)的方式来访问字符串中的子字符串(或字符) ,因为如果你查看 index 函数的定义,String.index (startIndex,offsetBy: 1)是 O (n)速度。我们当然可以这样做:

let characterArray = Array(string)

然后访问属性数组中的任何位置,但是 SPACE 的复杂度是 n = 字符串的长度 O (n) ,所以有点浪费空间。

我在看斯威夫特。字符串文档在 Xcode,有一个被冻结的公共结构称为 Index。我们可以初始化为:

let index = String.Index(encodedOffset: 0)

然后只需访问或打印 String 对象中的任何索引即可:

print(string[index])

注意: 小心不要越界

这样做很好,但是这样做的运行时间和空间复杂性是什么呢?好点了吗?