Obtenir le nième caractère d'une chaîne dans le langage de programmation Swift

419

Comment obtenir le nième caractère d'une chaîne? J'ai essayé l' []accessoire bracket ( ) sans succès.

var string = "Hello, world!"

var firstChar = string[0] // Throws error

ERREUR: 'indice' n'est pas disponible: ne peut pas inscrire une chaîne avec un Int, voir le commentaire de la documentation pour discussion

Mohsen
la source
1
Le message d'erreur «ne peut pas souscrire une chaîne avec un Int, voir le commentaire de la documentation pour discussion» semble faire référence à github.com/apple/swift/blob/master/stdlib/public/core/…
andrewdotn
utiliser à la var firstChar = string.index(string.startIndex, offsetBy: 0)place
Sazzad Hissain Khan
@SazzadHissainKhan, cela entraînerait un index de chaîne, pas un caractère. Btw pourquoi pas simplement string.startIndex? Pour le premier caractère string[string.startIndex] ou simplement string.first. Notez que la première approche dont vous auriez besoin pour vérifier si la chaîne est vide en premier la deuxième renvoie une option
Leo Dabus

Réponses:

567

Attention: Veuillez consulter la réponse de Leo Dabus pour une implémentation correcte de Swift 4 et Swift 5.

Swift 4 ou version ultérieure

Le Substringtype a été introduit dans Swift 4 pour rendre les sous-chaînes plus rapides et plus efficaces en partageant le stockage avec la chaîne d'origine, c'est donc ce que les fonctions d'indice devraient renvoyer.

Essayez-le ici

extension StringProtocol {
    subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] }
    subscript(range: Range<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence { self[..<index(startIndex, offsetBy: range.upperBound)] }
}

Pour convertir le Substringen un String, vous pouvez simplement le faire String(string[0..2]), mais vous ne devriez le faire que si vous prévoyez de conserver la sous-chaîne. Sinon, il est plus efficace de le garder a Substring.

Ce serait formidable si quelqu'un pouvait trouver un bon moyen de fusionner ces deux extensions en une seule. J'ai essayé d'étendre StringProtocol sans succès, car la indexméthode n'existe pas là-bas. Remarque: Cette réponse a déjà été modifiée, elle est correctement implémentée et fonctionne désormais également pour les sous-chaînes. Assurez-vous simplement d'utiliser une plage valide pour éviter de planter lorsque vous indiquez votre type StringProtocol. Pour un indice avec une plage qui ne se bloquera pas avec des valeurs hors plage, vous pouvez utiliser cette implémentation


Pourquoi n'est-ce pas intégré?

Le message d'erreur indique "voir le commentaire de la documentation pour la discussion" . Apple fournit l'explication suivante dans le fichier UnavailableStringAPIs.swift :

L'indexation de chaînes avec des nombres entiers n'est pas disponible.

Le concept de "le ie caractère dans une chaîne" a des interprétations différentes dans différentes bibliothèques et composants système. L'interprétation correcte doit être sélectionnée en fonction du cas d'utilisation et des API impliquées, elle String ne peut donc pas être indexée avec un entier.

Swift propose plusieurs façons d'accéder aux données de caractères stockées dans des chaînes.

  • String.utf8est une collection d'unités de code UTF-8 dans la chaîne. Utilisez cette API lors de la conversion de la chaîne en UTF-8. La plupart des API POSIX traitent les chaînes en termes d'unités de code UTF-8.

  • String.utf16est une collection d'unités de code UTF-16 en chaîne. La plupart des API Cocoa et Cocoa touch traitent les chaînes en termes d'unités de code UTF-16. Par exemple, des instances NSRangeutilisées avec NSAttributedStringet NSRegularExpressionstockent des décalages et des longueurs de sous-chaîne en termes d'unités de code UTF-16.

  • String.unicodeScalarsest une collection de scalaires Unicode. Utilisez cette API lorsque vous effectuez une manipulation de bas niveau des données de caractères.

  • String.characters est une collection de grappes de graphèmes étendues, qui sont une approximation des caractères perçus par l'utilisateur.

Notez que lors du traitement de chaînes contenant du texte lisible par l'homme, le traitement caractère par caractère doit être évité autant que possible. Utilisez des algorithmes Unicode sensibles aux paramètres de niveau élevé à la place, par exemple, String.localizedStandardCompare(), String.localizedLowercaseString, String.localizedStandardRangeOfString()etc.

aleclarson
la source
4
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'... String.Indexet Intne sont pas compatibles.
7
Si vous voyez, Cannot subscript a value of type 'String'...vérifiez cette réponse: stackoverflow.com/a/31265316/649379
SoftDesigner
24
Lorsque j'essaie de l'utiliser, je reçois Ambiguous use of 'subscript'.
jowie
18
ATTENTION! L'extension ci-dessous est horriblement inefficace. Chaque fois qu'une chaîne est accédée avec un entier, une fonction O (n) pour avancer son index de départ est exécutée. L'exécution d'une boucle linéaire à l'intérieur d'une autre boucle linéaire signifie que cette boucle est accidentellement O (n2) - à mesure que la longueur de la chaîne augmente, le temps que prend cette boucle augmente de façon quadratique. Au lieu de cela, vous pouvez utiliser la collection de chaînes de caractères.
ignaciohugog
2
fatal error: Can't form a Character from an empty String
Devin B
341

Swift 5.2

let str = "abcdef"
str[1 ..< 3] // returns "bc"
str[5] // returns "f"
str[80] // returns ""
str.substring(fromIndex: 3) // returns "def"
str.substring(toIndex: str.length - 2) // returns "abcd"

Vous devrez ajouter cette extension String à votre projet (elle est entièrement testée):

extension String {

    var length: Int {
        return count
    }

    subscript (i: Int) -> String {
        return self[i ..< i + 1]
    }

    func substring(fromIndex: Int) -> String {
        return self[min(fromIndex, length) ..< length]
    }

    func substring(toIndex: Int) -> String {
        return self[0 ..< max(0, toIndex)]
    }

    subscript (r: Range<Int>) -> String {
        let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                            upper: min(length, max(0, r.upperBound))))
        let start = index(startIndex, offsetBy: range.lowerBound)
        let end = index(start, offsetBy: range.upperBound - range.lowerBound)
        return String(self[start ..< end])
    }
}

Même si Swift avait toujours une solution prête à l'emploi à ce problème (sans l'extension String, que j'ai fournie ci-dessous), je recommanderais toujours fortement d' utiliser l'extension. Pourquoi? Parce que cela m'a fait gagner des dizaines d'heures de migration pénible à partir des premières versions de Swift, où la syntaxe de String changeait presque à chaque version, mais tout ce que j'avais à faire était de mettre à jour l'implémentation de l'extension plutôt que de refactoriser l'ensemble du projet. Fais ton choix.

let str = "Hello, world!"
let index = str.index(str.startIndex, offsetBy: 4)
str[index] // returns Character 'o'

let endIndex = str.index(str.endIndex, offsetBy:-2)
str[index ..< endIndex] // returns String "o, worl"

String(str.suffix(from: index)) // returns String "o, world!"
String(str.prefix(upTo: index)) // returns String "Hell"
nalexn
la source
changer range.upperBound - range.lowerBoundpourrange.count
Leo Dabus
Cela ne fait pas partie de la question d'origine, mais ... ce serait bien si cette affectation était également prise en charge. Par exemple, s [i] = "a" :).
Chris Prince
2
Je pense qu'avec Swift 4.2, les abonnements ne sont plus disponibles. Je reçois une erreur disant: 'indice' n'est pas disponible: ne peut pas souscrire une chaîne avec un Int, voir le commentaire de documentation pour discussion
C0D3
2
@ChrisPrinceextension StringProtocol where Self: RangeReplaceableCollection { subscript(offset: Int) -> Element { get { return self[index(startIndex, offsetBy: offset)] } set { let start = index(startIndex, offsetBy: offset) replaceSubrange(start..<index(after: start), with: [newValue]) } } }
Leo Dabus
Cela devrait être des fonctions intégrées
Ammar Mujeeb
150

Je viens de trouver cette solution de contournement soignée

var firstChar = Array(string)[0]
Jens Wirth
la source
2
Il s'agit d'une bonne solution rapide pour le cas (courant) où vous savez que vous avez des chaînes codées UTF8 ou ASCII. Assurez-vous simplement que les chaînes ne seront jamais dans un encodage qui utilise plus d'un octet.
Jeff Hay
48
Cela semble extrêmement inefficace car vous copiez la chaîne entière juste pour obtenir le premier caractère. Utilisez chaîne [chaîne. startIndex] au lieu de 0 comme l'a souligné Sulthan.
Bjorn
6
Déballer la chaîne: var firstChar = Array(string!)[0]Sinon, cela diraadd arrayLiteral
Mohammad Zaid Pathan
2
Je ne pense pas que ce soit propre, en fait, c'est un détour. Je ne sais pas exactement quel initialiseur dans Array est utilisé en premier, ce qui se produit (et je suppose que c'est l'initialiseur SequenceType qui le fait rassembler les caractères de la chaîne en tant que composants individuels pour le tableau). Ce n'est pas explicite du tout et peut être corrigé à l'avenir sans transtypage de type. Cela ne fonctionne pas non plus si vous utilisez un raccourci pour le tableau via [string] .first. La solution de @ Sulthan fonctionne mieux pour utiliser les valeurs d'index cuites. C'est beaucoup plus clair sur ce qui se passe ici.
TheCodingArt
2
Wow, faute de segment de compilateur!
Frank
124

Pas d'indexation en utilisant des entiers, uniquement en utilisant String.Index. Surtout avec une complexité linéaire. Vous pouvez également créer des plages à partir de ces String.Indexsous-chaînes et les utiliser.

Swift 3.0

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.index(before: someString.endIndex)]
let charAtIndex = someString[someString.index(someString.startIndex, offsetBy: 10)]

let range = someString.startIndex..<someString.index(someString.startIndex, offsetBy: 10)
let substring = someString[range]

Swift 2.x

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.endIndex.predecessor()]
let charAtIndex = someString[someString.startIndex.advanceBy(10)]

let range = someString.startIndex..<someString.startIndex.advanceBy(10)
let subtring = someString[range]

Notez que vous ne pouvez jamais utiliser un index (ou une plage) créé à partir d'une chaîne vers une autre chaîne

let index10 = someString.startIndex.advanceBy(10)

//will compile
//sometimes it will work but sometimes it will crash or result in undefined behaviour
let charFromAnotherString = anotherString[index10]
Sulthan
la source
7
Stringles index sont uniques à une chaîne. Cela est dû au fait que différentes chaînes peuvent avoir différents UTF-16 multi-unités Characterset / ou à différentes positions, de sorte que les index des unités UTF-16 ne correspondent pas, peuvent tomber au-delà de la fin ou du point à l'intérieur d'un UTF-16 multi-unités Character.
zaph
@Zaph C'est évident.
Sulthan
3
Expliquant pourquoi vous dites: "Parfois, cela va planter ou entraîner un comportement indéfini". Peut-être vaut-il mieux dire de ne pas le faire parce que ...
zaph
1
@Sulthan ..est maintenant ..<(dans votre affectation à range)
Aaron Brager
3
@CajunLuke Je sais que cela fait un moment que vous n'avez pas posté ce commentaire, mais jetez un œil à cette réponse . Vous pouvez utiliservar lastChar = string[string.endIndex.predecessor()]
David L
122

Xcode 11 • Swift 5.1

Vous pouvez étendre StringProtocol pour rendre l'index disponible également aux sous-chaînes:

extension StringProtocol {
    subscript(_ offset: Int)                     -> Element     { self[index(startIndex, offsetBy: offset)] }
    subscript(_ range: Range<Int>)               -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: ClosedRange<Int>)         -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: PartialRangeThrough<Int>) -> SubSequence { prefix(range.upperBound.advanced(by: 1)) }
    subscript(_ range: PartialRangeUpTo<Int>)    -> SubSequence { prefix(range.upperBound) }
    subscript(_ range: PartialRangeFrom<Int>)    -> SubSequence { suffix(Swift.max(0, count-range.lowerBound)) }
}

extension LosslessStringConvertible {
    var string: String { .init(self) }
}

extension BidirectionalCollection {
    subscript(safe offset: Int) -> Element? {
        guard !isEmpty, let i = index(startIndex, offsetBy: offset, limitedBy: index(before: endIndex)) else { return nil }
        return self[i]
    }
}

Essai

let test = "Hello USA 🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[safe: 10]   // "🇺🇸"
test[11]   // "!"
test[10...]   // "🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[10..<12]   // "🇺🇸!"
test[10...12]   // "🇺🇸!!"
test[...10]   // "Hello USA 🇺🇸"
test[..<10]   // "Hello USA "
test.first   // "H"
test.last    // "!"

// Subscripting the Substring
 test[...][...3]  // "Hell"

// Note that they all return a Substring of the original String.
// To create a new String from a substring
test[10...].string  // "🇺🇸!!! Hello Brazil 🇧🇷!!!"
Leo Dabus
la source
Puis-je demander ce qu'est "self [index (startIndex, offsetBy: i)]"? Et comment fonctionne «soi [i]»?
allenlinli
1
Salut Leo, merci pour la solution! Je viens (aujourd'hui) de passer de Swift 2.3 à 3 et votre indice de solution (plage: plage <Int>) donne l'erreur "Argument supplémentaire 'limitedBy' dans l'appel". Que pensez-vous peut être faux?
Ahmet Akkök
@ AhmetAkkök êtes-vous sûr de ne pas avoir changé le code?
Leo Dabus
1
@Leo, il s'est avéré que je n'ai pas converti l'ensemble du projet, mais sur l'application et non sur l'extension, j'ai répété le processus pour l'application et l'extension et cela fonctionne bien maintenant. Votre aide est très appréciée!
Ahmet Akkök
c'est un code très compliqué. Quel est l'avantage par rapport return String(Array(characters)[range])à Swift 3?
Dan Rosenstark
69

Swift 4

let str = "My String"

Chaîne à l'index

let index = str.index(str.startIndex, offsetBy: 3)
String(str[index])    // "S"

Sous-chaîne

let startIndex = str.index(str.startIndex, offsetBy: 3)
let endIndex = str.index(str.startIndex, offsetBy: 7)
String(str[startIndex...endIndex])     // "Strin"

Premiers n caractères

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[..<startIndex])    // "My "

N derniers caractères

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[startIndex...])    // "String"

Swift 2 et 3

str = "My String"

** Chaîne à l'index **

Swift 2

let charAtIndex = String(str[str.startIndex.advancedBy(3)])  // charAtIndex = "S"

Swift 3

str[str.index(str.startIndex, offsetBy: 3)]

Sous-chaîne deIndex àIndex

Swift 2

let subStr = str[str.startIndex.advancedBy(3)...str.startIndex.advancedBy(7)] // subStr = "Strin"

Swift 3

str[str.index(str.startIndex, offsetBy: 3)...str.index(str.startIndex, offsetBy: 7)]

Premiers n caractères

let first2Chars = String(str.characters.prefix(2)) // first2Chars = "My"

N derniers caractères

let last3Chars = String(str.characters.suffix(3)) // last3Chars = "ing"
Warif Akhand Rishi
la source
24

Swift 2.0 à partir de Xcode 7 GM Seed

var text = "Hello, world!"

let firstChar = text[text.startIndex.advancedBy(0)] // "H"

Pour le nième caractère, remplacez 0 par n-1.

Édition: Swift 3.0

text[text.index(text.startIndex, offsetBy: 0)]


nb il existe des moyens plus simples de saisir certains caractères dans la chaîne

par exemple let firstChar = text.characters.first

Matt Le Fleur
la source
23

Si vous voyez Cannot subscript a value of type 'String'...utiliser cette extension:

Swift 3

extension String {
    subscript (i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    subscript (i: Int) -> String {
        return String(self[i] as Character)
    }

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

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

Swift 2.3

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = advance(startIndex, integerIndex)
        return self[index]
    }

    subscript(integerRange: Range<Int>) -> String {
        let start = advance(startIndex, integerRange.startIndex)
        let end = advance(startIndex, integerRange.endIndex)
        let range = start..<end
        return self[range]
    }
}

Source: http://oleb.net/blog/2014/07/swift-strings/

SoftDesigner
la source
19

Solution Swift 2.2:

L'extension suivante fonctionne dans Xcode 7, c'est une combinaison de cette solution et de la conversion de la syntaxe Swift 2.0.

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = startIndex.advancedBy(integerIndex)
        return self[index]
    }

    subscript(integerRange: Range<Int>) -> String {
        let start = startIndex.advancedBy(integerRange.startIndex)
        let end = startIndex.advancedBy(integerRange.endIndex)
        let range = start..<end
        return self[range]
    }
}
Dan Beaulieu
la source
14

La classe de chaîne rapide ne permet pas d'obtenir un caractère à un index spécifique en raison de sa prise en charge native des caractères UTF. La longueur variable d'un caractère UTF en mémoire rend impossible le passage direct à un caractère. Cela signifie que vous devez boucler manuellement la chaîne à chaque fois.

Vous pouvez étendre String pour fournir une méthode qui parcourra les caractères jusqu'à ce que votre index souhaité

extension String {
    func characterAtIndex(index: Int) -> Character? {
        var cur = 0
        for char in self {
            if cur == index {
                return char
            }
            cur++
        }
        return nil
    }
}

myString.characterAtIndex(0)!
drewag
la source
3
Vous pouvez déjà parcourir les chaînes: pour la lettre dans "foo" {println (letter)}
Doobeh
@Doobeh Je voulais dire boucler et retourner le personnage réel comme dans mon montage ci
drewag
agréable! C'est amusant de voir comment vous pouvez le parcourir, mais pas via un index. Swift se sent pythonique mais avec des bords plus durs.
Doobeh
J'ai trouvé en utilisant le myString.bridgeToObjectiveC().characterAtIndex(0)ou (string as NSString ).characterAtIndex(0) retourne une valeur Int du caractère
markhunte
4
Ne pas utiliser des NSStringméthodes pour accéder à des caractères individuels d'un natif Swift String- les deux mécanismes utilisent différents de comptage, de sorte que vous obtiendrez des résultats imprévisibles avec des caractères Unicode plus élevés. La première méthode doit être sûre (une fois les bogues Unicode de Swift traités).
Nate Cook
11

En passant, il existe quelques fonctions applicables directement à la représentation de chaîne de caractères d'une chaîne, comme ceci:

var string = "Hello, playground"
let firstCharacter = string.characters.first // returns "H"
let lastCharacter = string.characters.last // returns "d"

Le résultat est de type Caractère, mais vous pouvez le convertir en Chaîne.

Ou ca:

let reversedString = String(string.characters.reverse())
// returns "dnuorgyalp ,olleH" 

:-)

Frédéric Adda
la source
11

Swift 4

String(Array(stringToIndex)[index]) 

C'est probablement la meilleure façon de résoudre ce problème une seule fois. Vous souhaiterez probablement convertir la chaîne sous forme de tableau en premier, puis convertir à nouveau le résultat en chaîne. Sinon, un caractère sera retourné au lieu d'une chaîne.

L'exemple String(Array("HelloThere")[1])retournera "e" sous forme de chaîne.

(Array("HelloThere")[1] renverra "e" en tant que personnage.

Swift ne permet pas d'indexer les chaînes comme des tableaux, mais cela fait le travail, à la manière d'une force brute.

Harsh G.
la source
1
La duplication de l'ensemble du contenu de la chaîne vers un autre emplacement mémoire est contre-performante, en particulier pour les chaînes de grande taille. Nous ne devrions pas avoir besoin d'allocations de mémoire supplémentaires pour des tâches simples comme l'accès direct à la mémoire.
Cristik
7

Ma solution très simple:

Swift 4.1:

let myString = "Test string"
let index = 0
let firstCharacter = myString[String.Index(encodedOffset: index)]

Swift 5.1:

let firstCharacter = myString[String.Index.init(utf16Offset: index, in: myString)]
Linh Dao
la source
Fonctionne dans Swift 4.1
leanne
Solution la plus simple et maintenant avec l'exemple de Swift 5 :)
OhadM
@Linh Dao N'utilisez pas encodedOffset. encodedOffset est déconseillé: encodedOffset a été déconseillé car l'utilisation la plus courante est incorrecte.
Leo Dabus
@OhadM le plus simple ne signifie pas qu'il est correct ou au moins il ne fonctionnera pas comme vous le souhaitez Essayezlet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Leo Dabus
1
@OhadM Mon commentaire n'était qu'un avertissement. N'hésitez pas à l'utiliser si vous pensez qu'il se comporte comme vous l'attendez.
Leo Dabus
6

Vous pouvez le faire en convertissant String en tableau et l'obtenir par index spécifique en utilisant l'indice comme ci-dessous

var str = "Hello"
let s = Array(str)[2]
print(s)
Soeng Saravit
la source
1
Notez que cette solution entraînera une duplication de contenu, la rendant moins performante en termes de mémoire et de CPU.
Cristik
5

Je viens d'avoir le même problème. Faites simplement ceci:

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)
user3723247
la source
Cela échoue pour de nombreux Emoji et autres personnages qui occupent en fait plus d'une fois "caractère" dans un NSString.
rmaddy
5

Swift3

Vous pouvez utiliser la syntaxe en indice pour accéder au caractère à un index de chaîne particulier.

let greeting = "Guten Tag!"
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // a

Visitez https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html

ou nous pouvons faire une extension de chaîne dans Swift 4

extension String {
    func getCharAtIndex(_ index: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: index)]
    }
}

USAGE:

let foo = "ABC123"
foo.getCharAtIndex(2) //C
Hamed
la source
3

Ma solution est sur une seule ligne, en supposant que cadena est la chaîne et 4 est la nième position que vous souhaitez:

let character = cadena[advance(cadena.startIndex, 4)]

Simple ... Je suppose que Swift inclura plus de choses sur les sous-chaînes dans les futures versions.

Julio César Fernández Muñoz
la source
1
N'est-ce pas la même chose que var charAtIndex = string[advance(string.startIndex, 10)]dans la réponse de Sulthan?
Martin R
Oui, c'est la même solution avec un autre exemple comme l'a dit Sulthan. Désolé pour le duplicata. :) C'est facile deux personnes ont trouvé la même chose.
Julio César Fernández Muñoz
3

Swift 3: une autre solution (testée en aire de jeux)

extension String {
    func substr(_ start:Int, length:Int=0) -> String? {
        guard start > -1 else {
            return nil
        }

        let count = self.characters.count - 1

        guard start <= count else {
            return nil
        }

        let startOffset = max(0, start)
        let endOffset = length > 0 ? min(count, startOffset + length - 1) : count

        return self[self.index(self.startIndex, offsetBy: startOffset)...self.index(self.startIndex, offsetBy: endOffset)]
    }
}

Usage:

let txt = "12345"

txt.substr(-1) //nil
txt.substr(0) //"12345"
txt.substr(0, length: 0) //"12345"
txt.substr(1) //"2345"
txt.substr(2) //"345"
txt.substr(3) //"45"
txt.substr(4) //"5"
txt.substr(6) //nil
txt.substr(0, length: 1) //"1"
txt.substr(1, length: 1) //"2"
txt.substr(2, length: 1) //"3"
txt.substr(3, length: 1) //"4"
txt.substr(3, length: 2) //"45"
txt.substr(3, length: 3) //"45"
txt.substr(4, length: 1) //"5"
txt.substr(4, length: 2) //"5"
txt.substr(5, length: 1) //nil
txt.substr(5, length: -1) //nil
txt.substr(-1, length: -1) //nil
Peter Kreinz
la source
2

Mise à jour pour la sous-chaîne swift 2.0

public extension String {
    public subscript (i: Int) -> String {
        return self.substringWithRange(self.startIndex..<self.startIndex.advancedBy(i + 1))
    }

    public subscript (r: Range<Int>) -> String {
        get {
            return self.substringWithRange(self.startIndex.advancedBy(r.startIndex)..<self.startIndex.advancedBy(r.endIndex))
        }
    }

}
YannSteph
la source
2

Je pense qu'une réponse rapide pour obtenir le premier caractère pourrait être:

let firstCharacter = aString[aString.startIndex]

C'est tellement élégant et performant que:

let firstCharacter = Array(aString.characters).first

Mais .. si vous voulez manipuler et faire plus d'opérations avec des chaînes, vous pourriez penser créer une extension .. il y a une extension avec cette approche, elle est assez similaire à celle déjà publiée ici:

extension String {
var length : Int {
    return self.characters.count
}

subscript(integerIndex: Int) -> Character {
    let index = startIndex.advancedBy(integerIndex)
    return self[index]
}

subscript(integerRange: Range<Int>) -> String {
    let start = startIndex.advancedBy(integerRange.startIndex)
    let end = startIndex.advancedBy(integerRange.endIndex)
    let range = start..<end
    return self[range]
}

}

MAIS C'EST UNE IDÉE TERRIBLE !!

L'extension ci-dessous est horriblement inefficace. Chaque fois qu'une chaîne est accédée avec un entier, une fonction O (n) pour avancer son index de départ est exécutée. L'exécution d'une boucle linéaire à l'intérieur d'une autre boucle linéaire signifie que cette boucle est accidentellement O (n2) - à mesure que la longueur de la chaîne augmente, le temps que prend cette boucle augmente de façon quadratique.

Au lieu de cela, vous pouvez utiliser la collection de chaînes de caractères.

ignaciohugog
la source
2

Swift 3

extension String {

    public func charAt(_ i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    public subscript (i: Int) -> String {
        return String(self.charAt(i) as Character)
    }

    public subscript (r: Range<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

    public subscript (r: CountableClosedRange<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

}

Usage

let str = "Hello World"
let sub = str[0...4]

Trucs et astuces de programmation utiles (écrits par moi)

quemeful
la source
2

Obtenir et définir un indice (chaîne et sous-chaîne) - Swift 4.2

Swift 4.2, Xcode 10

J'ai basé ma réponse sur la réponse de @alecarlson . La seule grande différence est que vous pouvez obtenir un Substringou un Stringretour (et dans certains cas, un seul Character). Vous pouvez également getet setl'indice. Enfin, la mienne est un peu plus lourde et plus longue que la réponse de @alecarlson et en tant que telle, je vous suggère de la mettre dans un fichier source.


Extension:

public extension String {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            self.replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}
public extension Substring {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }

    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}
Noah Wilder
la source
Cela compense inutilement les deux index (début et fin) de startIndex. Vous pouvez simplement compenser l'index de fin à l'aide de range.count et compenser l'index de début
Leo Dabus
2

Swift 4.2

Cette réponse est idéale car elle s'étend Stringet tous ses Subsequences( Substring) en une seule extension

public extension StringProtocol {

    public subscript (i: Int) -> Element {
        return self[index(startIndex, offsetBy: i)]
    }

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

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

    public subscript (bounds: PartialRangeUpTo<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex..<end]
    }

    public subscript (bounds: PartialRangeThrough<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex...end]
    }

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

Usage

var str = "Hello, playground"

print(str[5...][...5][0])
// Prints ","
Noah Wilder
la source
Cela compense inutilement les deux index ( startet end) de startIndex. Vous pouvez simplement compenser l' endindex à l'aide de range.count et compenser l' startindex
Leo Dabus
2

À l'heure actuelle, l'indice (_ :) n'est pas disponible. Et on ne peut pas faire ça

str[0] 

avec string.We doit fournir "String.Index" Mais, comment pouvons-nous donner notre propre numéro d'index de cette façon, au lieu de cela, nous pouvons utiliser,

string[str.index(str.startIndex, offsetBy: 0)]
Yodagama
la source
Veuillez modifier votre réponse et ajouter du contexte en expliquant comment votre réponse résout le problème, au lieu de publier une réponse en code uniquement. De l'avis
Pedram Parsian
Pourquoi faire le décalage inutile? Pourquoi pas simplement string[string.startIndex]? BTW, le code ne se comporterait pas correctement / ne se compilerait pas, car vous avez utilisé deux noms de variables différents.
Cristik
2

Swift 4.2 ou version ultérieure

Gamme et indice de gamme partielle en utilisant Stringla indicespropriété de

Comme variation de la réponse agréable de @LeoDabus , nous pouvons ajouter une extension supplémentaire à DefaultIndicesdans le but de nous permettre de nous rabattre sur la indicespropriété de Stringlors de la mise en œuvre des indices personnalisés (par Intplages spécialisées et plages partielles) pour ces derniers.

extension DefaultIndices {
    subscript(at: Int) -> Elements.Index { index(startIndex, offsetBy: at) }
}

// Moving the index(_:offsetBy:) to an extension yields slightly
// briefer implementations for these String extensions.
extension String {
    subscript(range: Range<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start..<indices[start...][range.count]]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start...indices[start...][range.count]]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence {
        self[indices[range.lowerBound]...]
    }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence {
        self[...indices[range.upperBound]]
    }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence {
        self[..<indices[range.upperBound]]
    }
}

let str = "foo bar baz bax"
print(str[4..<6]) // "ba"
print(str[4...6]) // "bar"
print(str[4...])  // "bar baz bax"
print(str[...6])  // "foo bar"
print(str[..<6])  // "foo ba"

Merci @LeoDabus de m'avoir pointé vers l'utilisation de la indicespropriété comme (autre) alternative à la Stringsouscription!

dfri
la source
1
le seul inconvénient est que CountableClosedRange compensera les deux indices à partir de startIndex
Leo Dabus
1
@LeoDabus je vois. Oui, principalement Linux, mais pas beaucoup de Swift ces jours-ci: / J'utilise swiftenvquand je le fais, cependant, je suppose qu'il sera également mis à jour avec la version 4.2 bientôt.
dfri
1
@LeoDabus merci d'avoir mis à jour cette réponse au Swift moderne!
dfri
1
@LeoDabus beau travail! Il faudra que j'examine les détails plus tard, mais je me souviens que je n'ai jamais aimé que nous devions nous rabattre sur Foundation certains des types d'ensembles ordonnés / dénombrables.
dfri
1
Merci mon pote!!!
Leo Dabus
2

Swift 5.1.3:

Ajoutez une extension de chaîne:

extension String {

 func stringAt(_ i: Int) -> String { 
   return String(Array(self)[i]) 
 } 

 func charAt(_ i: Int) -> Character { 
  return Array(self)[i] 
 } 
}

let str = "Teja Kumar"
let str1: String = str.stringAt(2)  //"j"
let str2: Character = str.charAt(5)  //"k"
Teja Kumar Bethina
la source
1
Cela convertira la chaîne entière en un tableau de caractères à chaque fois que vous appelez cette propriété pour en extraire un seul caractère.
Leo Dabus
1

Le Stringtype de Swift ne fournit pas de characterAtIndexméthode car il existe plusieurs façons de coder une chaîne Unicode. Allez-vous avec UTF8, UTF16 ou autre chose?

Vous pouvez accéder aux CodeUnitcollections en récupérant les propriétés String.utf8et String.utf16. Vous pouvez également accéder à la UnicodeScalarcollection en récupérant la String.unicodeScalarspropriété.

Dans l'esprit de NSStringl'implémentation de, je retourne un unichartype.

extension String
{
    func characterAtIndex(index:Int) -> unichar
    {
        return self.utf16[index]
    }

    // Allows us to use String[index] notation
    subscript(index:Int) -> unichar
    {
        return characterAtIndex(index)
    }
}

let text = "Hello Swift!"
let firstChar = text[0]
Erik
la source
Cela échouera pour les caractères qui nécessitent plus de stockage que 16 bits. Fondamentalement, tout caractère Unicode au-delà de U + FFFF.
rmaddy
1

Afin de nourrir le sujet et de montrer des possibilités d'indices rapides, voici une petite chaîne basée sur des indices "substring-toolbox"

Ces méthodes sont sûres et ne dépassent jamais les index de chaîne

extension String {
    // string[i] -> one string char
    subscript(pos: Int) -> String { return String(Array(self)[min(self.length-1,max(0,pos))]) }

    // string[pos,len] -> substring from pos for len chars on the left
    subscript(pos: Int, len: Int) -> String { return self[pos, len, .pos_len, .left2right] }

    // string[pos, len, .right2left] -> substring from pos for len chars on the right
    subscript(pos: Int, len: Int, way: Way) -> String { return self[pos, len, .pos_len, way] }

    // string[range] -> substring form start pos on the left to end pos on the right
    subscript(range: Range<Int>) -> String { return self[range.startIndex, range.endIndex, .start_end, .left2right] }

    // string[range, .right2left] -> substring start pos on the right to end pos on the left
    subscript(range: Range<Int>, way: Way) -> String { return self[range.startIndex, range.endIndex, .start_end, way] }

    var length: Int { return countElements(self) }
    enum Mode { case pos_len, start_end }
    enum Way { case left2right, right2left }
    subscript(var val1: Int, var val2: Int, mode: Mode, way: Way) -> String {
        if mode == .start_end {
            if val1 > val2 { let val=val1 ; val1=val2 ; val2=val }
            val2 = val2-val1
        }
        if way == .left2right {
            val1 = min(self.length-1, max(0,val1))
            val2 = min(self.length-val1, max(1,val2))
        } else {
            let val1_ = val1
            val1 = min(self.length-1, max(0, self.length-val1_-val2 ))
            val2 = max(1, (self.length-1-val1_)-(val1-1) )
        }
        return self.bridgeToObjectiveC().substringWithRange(NSMakeRange(val1, val2))

        //-- Alternative code without bridge --
        //var range: Range<Int> = pos...(pos+len-1)
        //var start = advance(startIndex, range.startIndex)
        //var end = advance(startIndex, range.endIndex)
        //return self.substringWithRange(Range(start: start, end: end))
    }
}


println("0123456789"[3]) // return "3"

println("0123456789"[3,2]) // return "34"

println("0123456789"[3,2,.right2left]) // return "56"

println("0123456789"[5,10,.pos_len,.left2right]) // return "56789"

println("0123456789"[8,120,.pos_len,.right2left]) // return "01"

println("0123456789"[120,120,.pos_len,.left2right]) // return "9"

println("0123456789"[0...4]) // return "01234"

println("0123456789"[0..4]) // return "0123"

println("0123456789"[0...4,.right2left]) // return "56789"

println("0123456789"[4...0,.right2left]) // return "678" << because ??? range can wear endIndex at 0 ???
Luc-Olivier
la source
1

Une solution de type python, qui vous permet d'utiliser un index négatif,

var str = "Hello world!"
str[-1]        // "!"

pourrait être:

extension String {
    subscript (var index:Int)->Character{
        get {
            let n = distance(self.startIndex, self.endIndex)
            index %= n
            if index < 0 { index += n }
            return self[advance(startIndex, index)]
        }
    }
}

Soit dit en passant, cela peut valoir la peine de transposer toute la notation de tranche de python

Joseph Merdrignac
la source
Cela vous dérange d'écrire quelque chose qui compile pour Swift 4? dernier retour ... la ligne ne semble pas fonctionner La fonction Advance () n'est pas là je crois.
C0D3
1

Vous pouvez également convertir une chaîne en tableau de caractères comme ça:

let text = "My Text"
let index = 2
let charSequence = text.unicodeScalars.map{ Character($0) }
let char = charSequence[index]

C'est le moyen d'obtenir char à l'index spécifié en temps constant.

L'exemple ci-dessous ne fonctionne pas en temps constant, mais nécessite un temps linéaire. Donc, si vous avez beaucoup de recherche dans String par index, utilisez la méthode ci-dessus.

let char = text[text.startIndex.advancedBy(index)]
Marcin Kapusta
la source