215 votes

Trouver l'index d'un caractère dans une chaîne Swift

Il est temps d'admettre la défaite...

En Objective-C, je pourrais utiliser quelque chose comme :

NSString* str = @"abcdefghi";
[str rangeOfString:@"c"].location; // 2

Dans Swift, je vois quelque chose de similaire :

var str = "abcdefghi"
str.rangeOfString("c").startIndex

...mais ça me donne juste un String.Index que je peux utiliser pour souscrire à la chaîne originale, mais pas pour en extraire un emplacement.

Pour info, cela String.Index a un ivar privé appelé _position qui contient la bonne valeur. Je ne vois pas comment c'est exposé.

Je sais que je pourrais facilement ajouter ça à String moi-même. Je suis plus curieux de savoir ce qui me manque dans cette nouvelle API.

0 votes

Voici un projet GitHub qui contient de nombreuses méthodes d'extension pour la manipulation des chaînes de caractères Swift : github.com/iamjono/SwiftString

0 votes

La meilleure mise en œuvre que j'ai trouvée est ici : stackoverflow.com/a/32306142/4550651

0 votes

Vous devez faire la distinction entre les points de code Unicode et les groupes de graphèmes étendus ?

255voto

Sulthan Points 23360

Vous n'êtes pas le seul à ne pas avoir trouvé la solution.

String ne met pas en œuvre RandomAccessIndex . Probablement parce qu'ils permettent des caractères avec des longueurs d'octets différentes. C'est pourquoi nous devons utiliser countElements pour obtenir le nombre de caractères. Cela s'applique également aux positions. Le site _position est probablement un index dans le tableau brut d'octets et ils ne veulent pas l'exposer. Le site String.Index est censé nous protéger contre l'accès aux octets au milieu des caractères.

Cela signifie que tout index que vous obtenez doit être créé à partir de String.startIndex ou String.endIndex ( String met en œuvre BidirectionalIndex ). Tout autre indice peut être créé en utilisant succ ou pred méthodes.

Pour nous aider avec les indices, il existe un ensemble de fonctions :

let text = "abc"
let index2 = advance(text.startIndex, 2) //will call succ 2 times (facepalm)
let lastChar: Character = text[index2] //now we can index!

let range = text.rangeOfString("b")
var index: Int = distance(text.startIndex, range.startIndex) //will call succ/pred several times (facepalm)

Je suis sérieusement préoccupé par la performance des cordes. De plus, travailler avec String.Index est encombrant.

Notez que cette mise en œuvre présente le problème suivant : les plages créées pour une chaîne de caractères ne peuvent pas être utilisées de manière fiable pour une autre chaîne de caractères, par exemple :

var text: String = "abc"
var text2: String = "

1 votes

Aussi bizarre que cela puisse être, cela semble être la réponse. Espérons que ces deux fonctions de plage seront intégrées à la documentation avant la version finale.

0 votes

Quel type est range sur var range = text.rangeOfString("b")

0 votes

String.Index est un type ? Il semble que l'intervalle devrait être, eh bien, un intervalle : un indice de départ et une longueur, sinon on l'appelle indice. J'en déduis donc que l'équipe Swift se joue de la langue anglaise en appelant un index une plage ? (J'ignore l'unicode du plan 1, c'est un problème existant qui n'a pas été corrigé).

23voto

huiquhome Points 109
extension String {

    // MARK: - sub String
    func substringToIndex(index:Int) -> String {
        return self.substringToIndex(advance(self.startIndex, index))
    }
    func substringFromIndex(index:Int) -> String {
        return self.substringFromIndex(advance(self.startIndex, index))
    }
    func substringWithRange(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
        let end = advance(self.startIndex, range.endIndex)
        return self.substringWithRange(start..<end)
    }

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

    // MARK: - replace
    func replaceCharactersInRange(range:Range<Int>, withString: String!) -> String {
        var result:NSMutableString = NSMutableString(string: self)
        result.replaceCharactersInRange(NSRange(range), withString: withString)
        return result
    }
}

7 votes

J'ai pensé à le faire, mais je pense que le problème est que cela cache la sémantique de l'accès aux chaînes. Imaginez la création d'une API pour l'accès aux listes liées qui ressemblerait à l'API d'un tableau. Les gens voudraient écrire du code horriblement inefficace.

8voto

connor Points 9825

Je ne suis pas sûr de savoir comment extraire la position de String.Index, mais si vous êtes prêt à vous rabattre sur certains frameworks Objective-C, vous pouvez faire un pont vers Objective-C et le faire de la même manière qu'avant.

"abcdefghi".bridgeToObjectiveC().rangeOfString("c").location

Il semble que certaines méthodes de NSString n'ont pas encore été (ou ne le seront peut-être pas) portées sur String. On pense aussi à Contains.

0 votes

Il semble en fait que l'accès à la propriété d'emplacement de la valeur de retour soit suffisant pour que le compilateur déduise un type NSString, de sorte que l'option bridgeToObjectiveC() L'appel n'est pas nécessaire. Mon problème semble se manifester uniquement lors de l'appel rangeOfString sur les cordes Swift déjà existantes. Cela semble être un problème d'API...

0 votes

C'est intéressant. Je ne savais pas qu'il inférait dans ces cas-là. Eh bien, quand c'est déjà une chaîne, vous pouvez toujours utiliser le pont.

5voto

Logan Points 7458

Si vous voulez utiliser une NSString familière, vous pouvez la déclarer explicitement :

var someString: NSString = "abcdefghi"

var someRange: NSRange = someString.rangeOfString("c")

Je ne sais pas encore comment faire ça en Swift.

1 votes

Cela fonctionne certainement, et il semble que le compilateur soit assez agressif dans la déduction des types NSString pour vous. J'espérais vraiment une façon pure et simple de faire cela en Swift, puisque cela semble être un cas d'utilisation assez commun.

0 votes

Oui, je regarde autour de moi, mais je ne le vois pas. Il est possible qu'ils se soient concentrés sur les domaines non supportés par ObjC parce qu'ils peuvent combler ces lacunes sans perdre trop de capacités. Je pense tout haut :)

3voto

rdelmar Points 53270

Cela a marché pour moi,

var loc = "abcdefghi".rangeOfString("c").location
NSLog("%d", loc);

cela a aussi fonctionné,

var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);

0 votes

Les deux semblent se rabattre sur le comportement de NSString d'une manière ou d'une autre. Dans mon terrain de jeu, j'utilisais une variable intermédiaire pour la chaîne. var str = "abcdef"; str.rangeOfString("c").location soulève une erreur à propos de String.Index qui n'a pas de membre nommé location...

1 votes

Cela fonctionne simplement parce que "string" est NSString et non String de Swift.

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