Swift中的String.index是如何工作的

jslywgbw  于 2023-06-21  发布在  Swift
关注(0)|答案(5)|浏览(125)

我一直在用Swift 3更新我的一些旧代码和答案,但是当我使用Swift字符串和索引时,理解这些东西是一件痛苦的事情。
具体来说,我正在尝试以下内容:

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)

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

eulz3vhy

eulz3vhy1#

以下所有示例都使用

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的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.Index的原因是Swift中的字符在后台并不都是相同的长度。一个Swift字符可以由一个、两个甚至更多的Unicode码位组成。因此,每个唯一的字符串必须计算其字符的索引。
可以将这种复杂性隐藏在Int索引扩展之后,但我不愿意这样做。被提醒实际发生的事情是很好的。

bkhjykvo

bkhjykvo2#

如果你想隐藏这些函数的复杂性,你可以使用这个扩展:

let str = "😀 Hello"
str[0] //😀
extension String {
    subscript(_ index: Int) -> Character? {
        guard index >= 0, index < self.count else {
            return nil
        }

        return self[self.index(self.startIndex, offsetBy: index)]
    }
}
nzk0hqpo

nzk0hqpo3#

我很感激这个问题和所有的信息。对于String.Index,我有一个问题和一个答案。
我想看看是否有一个O(1)的方法来访问String中的Substring(或Character),因为string.index(startIndex,offsetBy:如果你看一下索引函数的定义,它的速度是O(n)。当然,我们可以这样做:

let characterArray = Array(string)

然后访问characterArray中的任何位置,然而SPACE复杂度是n =字符串长度,O(n),所以这有点浪费空间。
我在看Xcode中的Swift.String文档,有一个冻结的公共结构体,名为Index。我们可以初始化为:

let index = String.Index(encodedOffset: 0)

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

print(string[index])
  • 注意:注意不要越界`*

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

wgeznvg7

wgeznvg74#

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

}
fcy6dtqo

fcy6dtqo5#

在tableViewController中创建UITextView。我使用了函数:textViewDidChange,然后检查return-key-input。如果检测到返回键输入,则删除返回键输入,并解除键盘。

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

相关问题