web-dev-qa-db-fra.com

'indice' n'est pas disponible: impossible d'indiquer la chaîne avec un objet CountableClosedRange <Int>, voir le commentaire sur la documentation pour en savoir plus

Dans Swift 4, je reçois cette erreur lorsque j'essaie de prendre un Substring d'un String à l'aide de la syntaxe en indice.

'indice' n'est pas disponible: impossible d'indiquer la chaîne avec un objet CountableClosedRange, voir le commentaire sur la documentation pour en savoir plus

Par exemple:

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

Deux questions:

  1. Comment puis-je résoudre cette erreur?
  2. Où se trouve "le commentaire de documentation pour discussion" auquel il est fait référence dans l'erreur?
35
Barry Jones
  1. Si vous voulez utiliser des indices sur des chaînes comme "palindrome"[1..<3] et "palindrome"[1...3], utilisez ces extensions.

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

Pour Swift 3 remplace par return self[start...end] et return self[start..<end].

  1. Apple n'a pas intégré cela dans la langue Swift car la définition d'un "caractère" dépend de la manière dont la chaîne est codée. Un caractère peut comporter de 8 à 64 bits et la valeur par défaut est généralement UTF. Vous pouvez spécifier d'autres encodages de chaîne dans String.Index.

Ceci est la documentation à laquelle l'erreur Xcode fait référence.

Plus d'informations sur les encodages de chaînes comme UTF-8 et UTF-16

59
p-sun

Votre question (et votre réponse personnelle) a 2 problèmes:

L'indexation d'une chaîne avec Int n'a jamais été disponible dans la bibliothèque standard de Swift. Ce code est invalide depuis aussi longtemps que Swift existe:

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

La nouvelle String.Index(encodedOffset: ) renvoie un index au codage UTF-16 (16 bits). La chaîne de Swift utilise Extended Grapheme Cluster, qui peut prendre entre 8 et 64 bits pour stocker un caractère. Les émojis font une très bonne démonstration:

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)

En fait, obtenir le String.Index N'a pas du tout changé dans Swift 4, pour le meilleur ou pour le pire:

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)
19
Code Different
  1. Comment puis-je résoudre cette erreur?

Cette erreur signifie que vous ne pouvez pas utiliser un int au format indice - vous devez utiliser un String.Index, que vous pouvez initialiser avec un 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. Où se trouve "le commentaire de documentation pour discussion" auquel il est fait référence dans l'erreur?

Il se trouve sur GitHub dans le référentiel Swift de la bibliothèque standard, dans un fichier intitulé UnavailableStringAPIs.Swift.gyb, situé au bas d'un classeur verrouillé, coincé dans des toilettes désaffectées avec une pancarte indiquant "Méfiez-vous du léopard". lien

11
Barry Jones

Basé sur réponse de 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]
    }
}

Changements notables:

  • Maintenant, une extension de StringProtocol. Cela permet aux utilisateurs tels que Substring d'obtenir également ces indices.
  • Les index de fin sont décalés par rapport à l'index de début des limites plutôt qu'au début de la chaîne. Cela empêche de traverser deux fois depuis le début du String. La méthode index est O(n) où n est le décalage par rapport à i.
2
Justin Oroz

S'appuyant sur les réponses (p-Sun) et celles de Justin Oroz , voici deux extensions qui protègent contre les index non valides au-delà du début et de la fin d'une chaîne (ces extensions évitent également de réanalyser la chaîne). à partir du début, juste pour trouver l'index à la fin de la plage):

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])
    }
}
2
Chris Frederick
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])
    }
}
0
chan sam

Vous obtenez cette erreur parce que le résultat de l'indice avec la plage est Substring? pas Substring.

Vous devez utiliser le code suivant:

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