118 votes

Comment fonctionne String.Index en Swift

J'ai mis à jour certains de mes anciens codes et réponses avec Swift 3, mais lorsque je suis arrivé aux chaînes et à l'indexation de Swift, j'ai eu du mal à comprendre les choses.

Plus précisément, j'ai essayé ce qui suit :

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5) // error

où la deuxième ligne me donnait l'erreur suivante

La fonction 'advancedBy' n'est pas disponible : Pour avancer un index de n étapes, appelez 'index(_:offsetBy :)' sur l'instance de CharacterView qui a produit l'index.

Je vois que String dispose des méthodes suivantes.

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)

J'ai donc commencé à jouer avec jusqu'à ce que je les comprenne. J'ajoute une réponse ci-dessous pour montrer comment ils sont utilisés.

312voto

Suragch Points 197

enter image description here

Tous les exemples suivants utilisent

var str = "Hello, playground"

startIndex y endIndex

  • startIndex est l'indice du premier caractère
  • endIndex est l'indice après le dernier caractère.

Exemple

// character
str[str.startIndex] // H
str[str.endIndex]   // error: after last character

// range
let range = str.startIndex..<str.endIndex
str[range]  // "Hello, playground"

Avec le système Swift 4 plages unilatérales la gamme peut être simplifiée sous l'une des formes suivantes.

let range = str.startIndex...
let range = ..<str.endIndex

J'utiliserai la forme complète dans les exemples suivants pour des raisons de clarté, mais pour plus de lisibilité, vous voudrez probablement utiliser les plages unilatérales dans votre code.

after

Comme dans : index(after: String.Index)

  • after fait référence à l'indice du caractère qui suit directement l'indice donné.

Exemples

// 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

Comme dans : index(before: String.Index)

  • before fait référence à l'indice du caractère qui précède directement l'indice donné.

Exemples

// 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

Comme dans : index(String.Index, offsetBy: String.IndexDistance)

  • El offsetBy peut être positive ou négative et commence à partir de l'indice donné. Bien qu'il soit du type String.IndexDistance vous pouvez lui donner un Int .

Exemples

// 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

Comme dans : index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • El limitedBy est utile pour s'assurer que l'offset ne fait pas sortir l'index de ses limites. Il s'agit d'un indice limitatif. Comme il est possible que l'offset dépasse la limite, cette méthode renvoie un Optional. Elle renvoie nil si l'index est hors limites.

Exemple

// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
    str[index]  // p
}

Si le décalage avait été 77 au lieu de 7 alors le if aurait été ignorée.

Pourquoi String.Index est-il nécessaire ?

Ce serait beaucoup plus facile d'utiliser un Int pour les chaînes de caractères. La raison pour laquelle vous devez créer un nouvel String.Index pour chaque chaîne est que les caractères en Swift n'ont pas tous la même longueur. Un seul caractère Swift peut être composé d'un, de deux, voire de plusieurs points de code Unicode. Ainsi, chaque chaîne unique doit calculer les indices de ses caractères.

Il est possible de cacher cette complexité derrière une extension de l'indice Int, mais je suis réticent à le faire. Il est bon de se faire rappeler ce qui se passe réellement.

21 votes

Pourquoi startIndex être autre chose que 0 ?

19 votes

@RoboRobok : Parce que Swift travaille avec des caractères Unicode, qui sont constitués de "grappes de graphèmes", Swift n'utilise pas d'entiers pour représenter les emplacements d'index. Disons que votre premier caractère est un é . Il est en fait constitué de la e plus un \u{301} Représentation Unicode. Si vous utilisiez un index de zéro, vous obtiendriez soit le symbole e ou le caractère d'accent ("grave"), et non pas l'ensemble du groupe qui constitue le é . Utilisation du startIndex vous assure d'obtenir le groupe de graphèmes complet pour n'importe quel caractère.

3 votes

Dans Swift 4.0, chaque caractère Unicode est compté par 1. Ex : "".count // Now : 1, Avant : 2

1voto

c0d3Junk13 Points 870

J'apprécie cette question et toutes les informations qu'elle contient. J'ai quelque chose en tête qui est une sorte de question et de réponse quand il s'agit de String.Index.

J'essaie de voir s'il existe un moyen O(1) d'accéder à une sous-chaîne (ou un caractère) à l'intérieur d'une chaîne de caractères parce que string.index(startIndex, offsetBy : 1) est une vitesse O(n) si vous regardez la définition de la fonction index. Bien sûr, nous pouvons faire quelque chose comme :

let characterArray = Array(string)

puis d'accéder à n'importe quelle position dans le tableau de caractères, mais la complexité SPACE de cette opération est la suivante n = longueur de la chaîne, O(n) donc c'est un peu un gaspillage d'espace.

Je regardais la documentation de Swift.String dans Xcode et il y a une structure publique figée appelée Index . Nous pouvons initialiser est comme :

let index = String.Index(encodedOffset: 0)

Ensuite, il suffit d'accéder ou d'imprimer tout index dans notre objet String comme tel :

print(string[index])

Note : attention à ne pas sortir des limites `.

Cela fonctionne et c'est très bien, mais quelle est la complexité en termes d'exécution et d'espace de cette méthode ? Est-ce que c'est mieux ?

-1voto

Pavel Zavarin Points 1
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)
    }

}

-2voto

frogcjn Points 612

Solution complète pour Swift 4 :

OffsetIndexableCollection (String utilisant Int Index)

https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-

-5voto

Karl Mogensen Points 10

Créer un UITextView à l'intérieur d'un tableViewController. J'ai utilisé la fonction : textViewDidChange et ensuite vérifié l'entrée de la touche retour. ensuite, s'il a détecté l'entrée de la touche retour, il supprime l'entrée de la touche retour et rejette le clavier.

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()
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X