2016-04-12 7 views
0

Я создаю расширение для String, и я пытаюсь решить, какое правильное/ожидаемое/хорошее поведение было бы для оператора индекса. В настоящее время у меня есть это:Хорошее поведение для индекса

// Will crash on 0 length strings 
subscript(kIndex: Int) -> Character { 
    var index = kIndex 
    index = index < 0 ? 0 : index 
    index = index >= self.length ? self.length-1 : index 
    let i = self.startIndex.advancedBy(index) 
    return self.characters[i] 
} 

Это вызывает все значения вне диапазона строки будет ограничен до края строки. В то время как это уменьшает сбой при передаче плохого индекса на индекс, это не похоже на правильную вещь. Я не могу выставить исключение из индекса и не проверять индекс, вызывает ошибку BAD_INSTRUCTION, если индекс за пределами границ. Единственным другим вариантом, о котором я могу думать, является возвращение необязательного, но это кажется неудобным. Взвешивая варианты, я считаю, что это наиболее разумно, но я не думаю, что кто-либо, использующий это, ожидал бы, что плохой индекс вернет правильный результат.

Итак, мой вопрос: каково «стандартное» ожидаемое поведение оператора индекса и возвращает допустимый элемент из недопустимого индекса приемлемым/соответствующим? Благодарю.

+0

Я чувствую, что ожидаемое поведение было бы для плохого индекса, чтобы сбой. Вот как работают индексы Apple. Если кто-то обеспокоен тем, что индекс правильный, то это зависит от них, чтобы проверить, попадает ли их индекс в диапазон строки. В вышеприведенном случае также нужно проверить, пуста ли строка или нет. Этот тип работы не относится к индексу IMO. –

ответ

1

Если вы используете индекс на 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п раз (несмотря на то, что генерирует результат, который использовался в предыдущем проходе через цикл, когда он вызывал successorn-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).

+0

Спасибо за тщательный ответ. Рассмотрев ваш ответ, я решил удалить поддержку оператора индекса на моем расширении 'String'. У меня также есть вычисляемая переменная 'length', которую я рассматриваю также как удаление, так как это будет операция O (n), но она выглядит как операция O (1). Считаете ли вы, что это все равно будет чрезмерно обманывать? В очередной раз благодарим за помощь. – dudeman