Как работает 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()
}
Другие вопросы по тегам