Как работает String.Index в Swift
Я обновлял часть своего старого кода и ответов с помощью Swift 3, но когда я попал в Swift Strings и Indexing, мне было тяжело понимать вещи.
В частности, я пытался следующее:
let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5) // error
где вторая строка давала мне следующую ошибку
'advancedBy' недоступен: чтобы продвинуть индекс на n шагов, вызовите 'index(_:offsetBy:)' для экземпляра CharacterView, который создал индекс.
я вижу это 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)
Сначала это действительно смущало меня, поэтому я начал играть с ними, пока не понял их. Я добавляю ответ ниже, чтобы показать, как они используются.
6 ответов
Все следующие примеры использования
var str = "Hello, playground"
startIndex
а также endIndex
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
полезно, чтобы убедиться, что смещение не приводит к выходу индекса за пределы границ. Это ограничивающий показатель. Поскольку смещение может превышать лимит, этот метод возвращает значение 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, но я не хочу этого делать. Хорошо напоминать о том, что на самом деле происходит.
Если вы хотите скрыть сложность этих функций, вы можете использовать это расширение:
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)]
}
}
Я ценю этот вопрос и всю информацию с ним. У меня есть что-то вроде вопроса и ответа, когда дело касается String.Index.
Я пытаюсь увидеть, есть ли способ O(1) для доступа к подстроке (или символу) внутри строки, потому что string.index(startIndex, offsetBy: 1) имеет скорость O(n), если вы посмотрите на определение индексная функция. Конечно, мы можем сделать что-то вроде:
let characterArray = Array(string)
затем получить доступ к любой позиции в characterArray, однако сложность SPACE для этого
n
= длина строки, O(n), так что это пустая трата места.
Я смотрел документацию Swift.String в Xcode, и есть замороженная общедоступная структура с именем
Index
. Мы можем инициализировать это как:
let index = String.Index(encodedOffset: 0)
Затем просто откройте или распечатайте любой индекс в нашем объекте String как таковой:
print(string[index])
Примечание: будьте осторожны, чтобы не выйти за пределы поля.
Это работает, и это здорово, но какова сложность выполнения этого способа во время выполнения и в пространстве? Это лучше?
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)
}
}
Swift 4 Полное решение:
OffsetIndexableCollection (строка, использующая Int Index)
https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-
Я столкнулся с той же проблемой и в итоге нашел решение, соответствующее моим потребностям. У меня есть UITextView внутри tableViewController. Я использовал функцию: 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()
}