"индекс" недоступен: нельзя индексировать String с CountableClosedRange <Int>, см. комментарий к документации для обсуждения

В Swift 4 я получаю эту ошибку, когда пытаюсь использовать Substring для String, используя синтаксис подстроки.

"индекс" недоступен: нельзя индексировать String с CountableClosedRange, см. комментарий к документации для обсуждения

Например:

let myString: String = "foobar"
let mySubstring: Substring = myString[1..<3]

Два вопроса:

  • Как я могу решить эту ошибку?
  • Где "комментарий к документации для обсуждения", на который ссылается ошибка?

Ответ 1

  • Если вы хотите использовать индексы для строк, таких как "palindrome"[1..<3] и "palindrome"[1...3], используйте эти расширения.

Swift 4

extension String {
    subscript (bounds: CountableClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return String(self[start...end])
    }

    subscript (bounds: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return String(self[start..<end])
    }
}

Swift 3

Для Swift 3 замените на return self[start...end] и return self[start..<end].

  1. Apple не построила это на языке Swift, потому что определение "характера" зависит от того, как кодируется String. Символ может быть от 8 до 64 бит, а по умолчанию обычно UTF-16. Вы можете указать другие строковые кодировки в String.Index.

Это документация, на которую ссылается ошибка Xcode.

Подробнее о строковых кодировках, таких как UTF-8 и UTF-16

Ответ 2

В вашем вопросе (и самоответ) есть две проблемы:

Подпись строки с Int никогда не была доступна в стандартной библиотеке Swift. Этот код недействителен до тех пор, пока существует Swift:

let mySubstring: Substring = myString[1..<3]

Новый String.Index(encodedOffset: ) возвращает индекс в кодировке UTF-16 (16 бит). Строка Swift использует расширенный кластер Grapheme, который может принимать от 8 до 64 бит для хранения символа. Эможи делают очень хорошую демонстрацию:

let myString = "🇺🇸🇨🇦🇬🇧🇫🇷"
let lowerBound = String.Index(encodedOffset: 1)
let upperBound = String.Index(encodedOffset: 3)
let mySubstring = myString[lowerBound..<upperBound]

// Expected: Canadian and UK flags
// Actual  : gibberish
print(mySubstring)

Фактически, получение String.Index вообще не изменилось в Swift 4, лучше или хуже:

let myString = "🇺🇸🇨🇦🇬🇧🇫🇷"
let lowerBound = myString.index(myString.startIndex, offsetBy: 1)
let upperBound = myString.index(myString.startIndex, offsetBy: 3)
let mySubstring = myString[lowerBound..<upperBound]

print(mySubstring)

Ответ 3

  1. Как я могу устранить эту ошибку?

Эта ошибка означает, что вы не можете использовать Int в формате индекса - вы должны использовать String.Index, который вы можете инициализировать с помощью encodedOffset Int.

let myString: String = "foobar"
let lowerBound = String.Index.init(encodedOffset: 1)
let upperBound = String.Index.init(encodedOffset: 3)
let mySubstring: Substring = myString[lowerBound..<upperBound]
  1. Где находится "документальный комментарий для обсуждения", на который ссылается ошибка?

Это на GitHub в репозитории Swift Standard Library в файле с именем UnavailableStringAPIs.swift.gyb в нижней части запертого шкафа для хранения документов, который застрял в заброшенном туалете с табличкой на двери с надписью "Осторожно, леопард". ссылка на сайт

Ответ 4

Основано на ответе p-sun

Swift 4

extension StringProtocol {
    subscript(bounds: CountableClosedRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(start, offsetBy: bounds.count)
        return self[start..<end]
    }

    subscript(bounds: CountableRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(start, offsetBy: bounds.count)
        return self[start..<end]
    }
}

Заметные изменения:

  • Теперь расширение StringProtocol. Это позволяет таким пользователям, как Substring также получать эти подписки.
  • Конечные индексы смещены от начального индекса границ, а не от начала строки. Это предотвращает перемещение с начала String дважды. index методом является O (n), где n - смещение от i.

Ответ 5

Основываясь на ответах p-sun и Джастина Ороса, вот два расширения, которые защищают от недопустимых индексов за пределами начала и конца строки (эти расширения также избегают повторного сканирования строки с самого начала, чтобы найти индекс в конце диапазона ):

extension String {

    subscript(bounds: CountableClosedRange<Int>) -> String {
        let lowerBound = max(0, bounds.lowerBound)
        guard lowerBound < self.count else { return "" }

        let upperBound = min(bounds.upperBound, self.count-1)
        guard upperBound >= 0 else { return "" }

        let i = index(startIndex, offsetBy: lowerBound)
        let j = index(i, offsetBy: upperBound-lowerBound)

        return String(self[i...j])
    }

    subscript(bounds: CountableRange<Int>) -> String {
        let lowerBound = max(0, bounds.lowerBound)
        guard lowerBound < self.count else { return "" }

        let upperBound = min(bounds.upperBound, self.count)
        guard upperBound >= 0 else { return "" }

        let i = index(startIndex, offsetBy: lowerBound)
        let j = index(i, offsetBy: upperBound-lowerBound)

        return String(self[i..<j])
    }
}

Ответ 6

extension String {

    subscript(bounds: CountableClosedRange<Int>) -> String {
        let lowerBound = max(0, bounds.lowerBound)
        guard lowerBound < self.count else { return "" }

        let upperBound = min(bounds.upperBound, self.count-1)
        guard upperBound >= 0 else { return "" }

        let i = index(startIndex, offsetBy: lowerBound)
        let j = index(i, offsetBy: upperBound-lowerBound)

        return String(self[i...j])
    }

    subscript(bounds: CountableRange<Int>) -> String {
        let lowerBound = max(0, bounds.lowerBound)
        guard lowerBound < self.count else { return "" }

        ***let upperBound = min(bounds.upperBound, self.count-1)***
        guard upperBound >= 0 else { return "" }

        let i = index(startIndex, offsetBy: lowerBound)
        let j = index(i, offsetBy: upperBound-lowerBound)

        return String(self[i..<j])
    }
}

Ответ 7

Вы получаете эту ошибку, потому что результатом индекса с Substring? является Substring? не Substring.

Вы должны использовать следующий код:

let myString: String = "foobar"
let mySubstring: Substring? = myString[1..<3]