Если вы используете индекс на String
, вы можете сначала подумать о том, почему стандартная библиотека не хочет этого делать.
Когда вы звоните self.startIndex.advancedBy(index)
, вы эффективно писать что-то вроде этого:
var i = self.startIndex
while i < index { i = i.successor() }
Это происходит потому, что String.CharacterView.Index
не тип индекса произвольного доступа. См. Документы на странице advancedBy
. Индексы строк не являются случайным доступом, потому что каждый Character
в строке может представлять собой любое количество байтов в базовом хранилище строки - вы не можете просто получить символ n, переместив n * characterSize
в хранилище, как вы можете, с помощью строки C.
Так что, если бы нужно было использовать оператор подстрочного для перебора символов в строке:
for i in 0..<string.characters.count {
doSomethingWith(string[i])
}
... вы бы цикл, который выглядит как он работает в линейном времени, потому что он выглядит так же, как итерация массива - каждый проход через цикл должен занимать одинаковое количество времени, потому что каждый из них только увеличивает i
и использует постоянный доступ для получения string[i]
, правильно? Неа. advancedBy
вызова в первом проходе через цикл вызывает successor
один раз, последующие вызовы это дважды, и так далее ... если ваша строка имеет п символов, последний проход через петлю вызовов successor
п раз (несмотря на то, что генерирует результат, который использовался в предыдущем проходе через цикл, когда он вызывал successor
n-1 раз). Другими словами, вы только что сделали операцию O (n), которая выглядит как операция O (n), оставляя бомбу с производительностью для тех, кто еще использует ваш код.
Это цена полностью поддерживающей Unicode библиотеки строк.
Во всяком случае, чтобы ответить на ваш фактический вопрос - есть две школы для индексов и области проверки:
Есть дополнительный тип возврата: func subscript(index: Index) -> Element?
Это имеет смысл, когда есть нет разумного способа для клиента проверить, является ли индекс действительным, не выполняя ту же работу, что и поиск - например для словаря, узнав , если есть значение для заданного ключа, то это же самое, что найти что значение для ключа есть.
Необходимо, чтобы индекс был действительным и в противном случае произвел фатальную ошибку.
Обычный случай для этого - это ситуации, когда клиент вашего API может и должен проверять действительность до доступа к индексу. Это то, что делают массивы Swift, потому что массивы знают их количество, и вам не нужно заглядывать в массив, чтобы увидеть, действительно ли индекс.
Канонический тест для этого - precondition
: например.
func subscript(index: Index) -> Element {
precondition(isValid(index), "index must be valid")
// ... do lookup ...
}
(Здесь isValid
есть некоторые операции, специфичные для вашего класса для проверки индекса. - например, убедившись, что это> 0 и < подсчет)
практически в любом прецеденте, это не idiomatic Swift, чтобы вернуть «реальное» значение в случае плохого индекса, и нецелесообразно возвращать ценное значение - разделение внутриполосных значений от часовых - причина, по которой Swift имеет опции.
Какой из них более подходит для вашего случая использования ... ну, так как ваш случай использования проблематичен для того, чтобы быть с ним, это своего рода стирка. Если у вас precondition
этот индекс <, вы по-прежнему несете стоимость O (n), чтобы проверить это (потому что String
должен изучить его содержимое, чтобы выяснить, какие последовательности байтов составляют каждый символ, прежде чем он узнает, сколько символов у него есть). Если вы сделаете свой тип возврата дополнительным и возвращаете нуль после вызова advancedBy
или count
, вы по-прежнему понесли стоимость O (n).
Я чувствую, что ожидаемое поведение было бы для плохого индекса, чтобы сбой. Вот как работают индексы Apple. Если кто-то обеспокоен тем, что индекс правильный, то это зависит от них, чтобы проверить, попадает ли их индекс в диапазон строки. В вышеприведенном случае также нужно проверить, пуста ли строка или нет. Этот тип работы не относится к индексу IMO. –