Recherche de l'index du caractère dans la chaîne Swift

203

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

Dans 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 cela me donne juste un String.Index, que je peux utiliser pour réinscrire dans la chaîne d'origine, mais pas extraire un emplacement de.

FWIW, qui String.Indexa un ivar privé appelé _positionqui a la bonne valeur. Je ne vois tout simplement pas comment cela est exposé.

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

Matt Wilding
la source
Voici un projet GitHub qui contient de nombreuses méthodes d'extension pour la manipulation de chaînes Swift: github.com/iamjono/SwiftString
RenniePet
La meilleure implémentation que j'ai trouvée est ici: stackoverflow.com/a/32306142/4550651
Carlos García
Avez-vous besoin de faire la distinction entre les points de code Unicode et les grappes de graphèmes étendues?
Ben Leggiero

Réponses:

248

Vous n'êtes pas le seul à ne pas trouver la solution.

Stringne met pas en œuvre RandomAccessIndexType. Probablement parce qu'ils permettent des caractères avec des longueurs d'octets différentes. C'est pourquoi nous devons utiliser string.characters.count( countou countElementsdans Swift 1.x) pour obtenir le nombre de caractères. Cela s'applique également aux postes. Il _positions'agit probablement d'un index dans le tableau brut d'octets et ils ne veulent pas l'exposer. Le String.Indexest destiné à nous protéger de l'accès aux octets au milieu des caractères.

Cela signifie que tout index que vous obtenez doit être créé à partir de String.startIndexou String.endIndex( String.Indeximplémente BidirectionalIndexType). Tout autre indice peut être créé à l'aide des méthodes successorou predecessor.

Maintenant, pour nous aider avec les indices, il existe un ensemble de méthodes (fonctions dans Swift 1.x):

Swift 4.x

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

let characterIndex2 = text.index(text.startIndex, offsetBy: 2)
let lastChar2 = text[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 3.0

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

let characterIndex2 = text.characters.index(text.characters.startIndex, offsetBy: 2)
let lastChar2 = text.characters[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 2.x

let text = "abc"
let index2 = text.startIndex.advancedBy(2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!
let lastChar2 = text.characters[index2] //will do the same as above

let range: Range<String.Index> = text.rangeOfString("b")!
let index: Int = text.startIndex.distanceTo(range.startIndex) //will call successor/predecessor several times until the indices match

Swift 1.x

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

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

Travailler avec String.Indexest lourd mais utiliser un wrapper pour indexer par des entiers (voir https://stackoverflow.com/a/25152652/669586 ) est dangereux car il cache l'inefficacité d'une véritable indexation.

Notez que l'implémentation de l'indexation Swift a le problème que les index / plages créés pour une chaîne ne peuvent pas être utilisés de manière fiable pour une chaîne différente , par exemple:

Swift 2.x

let text: String = "abc"
let text2: String = "🎾🏇🏈"

let range = text.rangeOfString("b")!

//can randomly return a bad substring or throw an exception
let substring: String = text2[range]

//the correct solution
let intIndex: Int = text.startIndex.distanceTo(range.startIndex)
let startIndex2 = text2.startIndex.advancedBy(intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]

Swift 1.x

let text: String = "abc"
let text2: String = "🎾🏇🏈"

let range = text.rangeOfString("b")

//can randomly return nil or a bad substring 
let substring: String = text2[range] 

//the correct solution
let intIndex: Int = distance(text.startIndex, range.startIndex)    
let startIndex2 = advance(text2.startIndex, intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]  
Sulthan
la source
1
Cela peut sembler gênant, cela semble être la réponse. Espérons que ces deux fonctions de gamme seront intégrées dans la documentation quelque temps avant la version finale.
Matt Wilding
De quel type est rangeenvar range = text.rangeOfString("b")
Zaph
5
@Zaph Every Collectiona un typealias IndexType. Pour les tableaux, il est défini comme Int, car Stringil est défini comme String.Index. Les tableaux et les chaînes peuvent également utiliser des plages (pour créer des sous-réseaux et des sous-chaînes). La gamme est un type spécial Range<T>. Pour les chaînes, c'est Range<String.Index>pour les tableaux Range<Int>.
Sulthan
1
In Swift 2.0, distance(text.startIndex, range.startIndex)devienttext.startIndex.distanceTo(range.startIndex)
superarts.org
1
@devios String, exactement comme NSStringdans Foundation, a une méthode appelée hasPrefix(_:).
Sulthan
86

Swift 3.0 rend cela un peu plus détaillé:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.index(of: needle) {
    let pos = string.characters.distance(from: string.startIndex, to: idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

Extension:

extension String {
    public func index(of char: Character) -> Int? {
        if let idx = characters.index(of: char) {
            return characters.distance(from: startIndex, to: idx)
        }
        return nil
    }
}

Dans Swift 2.0, cela est devenu plus facile:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.indexOf(needle) {
    let pos = string.startIndex.distanceTo(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

Extension:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = self.characters.indexOf(char) {
            return self.startIndex.distanceTo(idx)
        }
        return nil
    }
}

Implémentation de Swift 1.x:

Pour une solution Swift pure, on peut utiliser:

let string = "Hello.World"
let needle: Character = "."
if let idx = find(string, needle) {
    let pos = distance(string.startIndex, idx)
    println("Found \(needle) at position \(pos)")
}
else {
    println("Not found")
}

En extension à String:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = find(self, char) {
            return distance(self.startIndex, idx)
        }
        return nil
    }
}
Pascal
la source
1
caractères est obsolète !!
Shivam Pokhriyal
23
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
    }
}
huiquhome
la source
7
J'ai pensé à faire cela, mais je pense que c'est un problème qu'il cache la sémantique de l'accès aux chaînes. Imaginez que vous créez une API pour accéder aux listes liées qui ressemble à l'API d'un tableau. Les gens aimeraient écrire du code horriblement inefficace.
Erik Engheim
16

J'ai trouvé cette solution pour swift2:

var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2
VYT
la source
quel sera l'index lorsque str = "abcdefgchi"
Vatsal Shukla
10

Swift 5.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return firstIndex(of: char)?.utf16Offset(in: self)
  }
}

Swift 4.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return index(of: char)?.encodedOffset        
  }
}
Vincenso
la source
return index (of: element) .map {target.distance (from: startIndex, to: $ 0)}
frogcjn
8

Je ne sais pas comment extraire la position de String.Index, mais si vous êtes prêt à vous rabattre sur certains cadres Objective-C, vous pouvez passer à objective-c et le faire de la même manière que vous le faisiez auparavant.

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

Il semble que certaines méthodes NSString n'aient pas encore été (ou ne seront peut-être pas) portées sur String. Contient aussi vient à l'esprit.

Connor
la source
Il apparaît en fait que l'accès à la propriété location de la valeur de retour est suffisant pour que le compilateur infère un type NSString, donc l' bridgeToObjectiveC()appel n'est pas nécessaire. Mon problème semble se manifester uniquement lors de l'appel rangeOfStringà une chaîne Swift existante. Cela ressemble à un problème d'API ...
Matt Wilding
c'est intéressant. Je ne savais pas que cela était inféré dans ces cas. Eh bien, quand c'est déjà une chaîne, vous pouvez toujours utiliser le pont.
Connor
8

Voici une extension de chaîne propre qui répond à la question:

Swift 3:

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

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).range(of: target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).range(of: target, options: NSString.CompareOptions.backwards)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.range(of: s) != nil) ? true : false
    }
}

Swift 2.2:

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

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).rangeOfString(target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).rangeOfString(target, options: NSStringCompareOptions.BackwardsSearch)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.rangeOfString(s) != nil) ? true : false
    }
}
YannSteph
la source
7

Vous pouvez également trouver des index d'un caractère dans une seule chaîne comme celle-ci,

extension String {

  func indexes(of character: String) -> [Int] {

    precondition(character.count == 1, "Must be single character")

    return self.enumerated().reduce([]) { partial, element  in
      if String(element.element) == character {
        return partial + [element.offset]
      }
      return partial
    }
  }

}

Ce qui donne le résultat dans [String.Distance] ie. [Int], comme

"apple".indexes(of: "p") // [1, 2]
"element".indexes(of: "e") // [0, 2, 4]
"swift".indexes(of: "j") // []
Sandeep
la source
5

Si vous souhaitez utiliser NSString familier, vous pouvez le déclarer explicitement:

var someString: NSString = "abcdefghi"

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

Je ne sais pas encore comment faire cela dans Swift.

Logan
la source
1
Cela fonctionne certainement, et il semble que le compilateur soit assez agressif pour déduire les types NSString pour vous. J'espérais vraiment une méthode Swift pure, car cela semble être un cas d'utilisation assez courant.
Matt Wilding
Oui, je regarde autour de moi, mais je ne le vois pas. Il est possible qu'ils se soient concentrés sur des domaines non pris en charge par ObjC car ils peuvent combler ces lacunes sans trop de capacités perdues. Je pense juste à haute voix :)
Logan
4

Si vous voulez connaître la position d'un caractère dans une chaîne en tant que valeur int , utilisez ceci:

let loc = newString.range(of: ".").location
Un développeur allemand
la source
Swift 5: value of type 'Range<String.Index>?' has no member 'location'.
Neph
3

Je sais que cela est ancien et qu'une réponse a été acceptée, mais vous pouvez trouver l'index de la chaîne dans quelques lignes de code en utilisant:

var str : String = "abcdefghi"
let characterToFind: Character = "c"
let characterIndex = find(str, characterToFind)  //returns 2

Quelques autres bonnes informations sur les cordes Swift ici Strings in Swift

Jack
la source
findn'est pas disponible dans Swift 3
AamirR
2

Cela a fonctionné pour moi,

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

cela a fonctionné aussi,

var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);
rdelmar
la source
Les deux semblent se replier 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").locationsoulève une erreur à propos de String.Index n'ayant pas de membre nommé location ...
Matt Wilding
1
Cela fonctionne simplement parce que "string" est NSString et non Swift's String
gderaco
2

La chaîne de type variable dans Swift contient des fonctions différentes par rapport à NSString dans Objective-C. Et comme Sulthan l'a mentionné,

Swift String n'implémente pas RandomAccessIndex

Ce que vous pouvez faire est de convertir votre variable de type String en NSString (ceci est valide dans Swift). Cela vous donnera accès aux fonctions de NSString.

var str = "abcdefghi" as NSString
str.rangeOfString("c").locationx   // returns 2
Alexander G
la source
2

Si vous y réfléchissez, vous n'avez en fait pas vraiment besoin de la version Int exacte de l'emplacement. La plage ou même le String.Index est suffisant pour extraire à nouveau la sous-chaîne si nécessaire:

let myString = "hello"

let rangeOfE = myString.rangeOfString("e")

if let rangeOfE = rangeOfE {
    myString.substringWithRange(rangeOfE) // e
    myString[rangeOfE] // e

    // if you do want to create your own range
    // you can keep the index as a String.Index type
    let index = rangeOfE.startIndex
    myString.substringWithRange(Range<String.Index>(start: index, end: advance(index, 1))) // e

    // if you really really need the 
    // Int version of the index:
    let numericIndex = distance(index, advance(index, 1)) // 1 (type Int)
}
NatashaTheRobot
la source
La meilleure réponse pour moi est que func indexOf (target: String) -> Int {return (self as NSString) .rangeOfString (target) .location}
YannSteph
2

La manière la plus simple est:

Dans Swift 3 :

 var textViewString:String = "HelloWorld2016"
    guard let index = textViewString.characters.index(of: "W") else { return }
    let mentionPosition = textViewString.distance(from: index, to: textViewString.endIndex)
    print(mentionPosition)
Pragnesh Vitthani
la source
1

La chaîne est un type de pont pour NSString, alors ajoutez

import Cocoa

à votre fichier rapide et utilisez toutes les "anciennes" méthodes.

IgnazioC
la source
1

En termes de réflexion, cela pourrait être appelé une INVERSION. Vous découvrez que le monde est rond au lieu d'être plat. "Vous n'avez pas vraiment besoin de connaître l'INDEX du personnage pour faire des choses avec." Et en tant que programmeur C, j'ai trouvé cela difficile à prendre aussi! Votre ligne "let index = letters.characters.indexOf (" c ")!" est suffisant en soi. Par exemple, pour supprimer le c que vous pourriez utiliser ... (coller le terrain de jeu)

    var letters = "abcdefg"
  //let index = letters.rangeOfString("c")!.startIndex //is the same as
    let index = letters.characters.indexOf("c")!
    range = letters.characters.indexOf("c")!...letters.characters.indexOf("c")!
    letters.removeRange(range)
    letters

Cependant, si vous voulez un index, vous devez renvoyer un INDEX réel et non un Int, car une valeur Int nécessiterait des étapes supplémentaires pour toute utilisation pratique. Ces extensions renvoient un index, un décompte d'un caractère spécifique et une plage que ce code plug-in capable de jeu va démontrer.

extension String
{
    public func firstIndexOfCharacter(aCharacter: Character) -> String.CharacterView.Index? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                return index
            }

        }
        return nil
    }

    public func returnCountOfThisCharacterInString(aCharacter: Character) -> Int? {

        var count = 0
        for letters in self.characters{

            if aCharacter == letters{

                count++
            }
        }
        return count
    }


    public func rangeToCharacterFromStart(aCharacter: Character) -> Range<Index>? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                let range = self.startIndex...index
                return range
            }

        }
        return nil
    }

}



var MyLittleString = "MyVery:important String"

var theIndex = MyLittleString.firstIndexOfCharacter(":")

var countOfColons = MyLittleString.returnCountOfThisCharacterInString(":")

var theCharacterAtIndex:Character = MyLittleString[theIndex!]

var theRange = MyLittleString.rangeToCharacterFromStart(":")
MyLittleString.removeRange(theRange!)
Homme de montagne
la source
1

Solution complète Swift 4:

OffsetIndexableCollection (chaîne utilisant l'index Int)

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

let a = "01234"

print(a[0]) // 0
print(a[0...4]) // 01234
print(a[...]) // 01234

print(a[..<2]) // 01
print(a[...2]) // 012
print(a[2...]) // 234
print(a[2...3]) // 23
print(a[2...2]) // 2

if let number = a.index(of: "1") {
    print(number) // 1
    print(a[number...]) // 1234
}

if let number = a.index(where: { $0 > "1" }) {
    print(number) // 2
}
grenouille
la source
1

extension String {

//Fucntion to get the index of a particular string
func index(of target: String) -> Int? {
    if let range = self.range(of: target) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}
//Fucntion to get the last index of occurence of a given string
func lastIndex(of target: String) -> Int? {
    if let range = self.range(of: target, options: .backwards) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}

}

Uddiptta Ujjawal
la source
1

Swift 5

Trouver l'index de la sous-chaîne

let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
    let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
    print("index: ", index) //index: 2
}
else {
    print("substring not found")
}

Trouver l'index du personnage

let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
    let index: Int = str.distance(from: str.startIndex, to: firstIndex)
    print("index: ", index)   //index: 2
}
else {
    print("symbol not found")
}
Viktor
la source
0

Si vous cherchez un moyen facile d'obtenir l'index des caractères ou des chaînes, consultez cette bibliothèque http://www.dollarswift.org/#indexof-char-character-int

Vous pouvez obtenir l'indexOf d'une chaîne en utilisant également une autre chaîne ou un modèle d'expression régulière

Encore PTL
la source
0

Pour obtenir l'index d'une sous-chaîne dans une chaîne avec Swift 2:

let text = "abc"
if let range = text.rangeOfString("b") {
   var index: Int = text.startIndex.distanceTo(range.startIndex) 
   ...
}
Tony
la source
0

Dans swift 2.0

var stringMe="Something In this.World"
var needle="."
if let idx = stringMe.characters.indexOf(needle) {
    let pos=stringMe.substringFromIndex(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}
Yoosaf Abdulla
la source
0
let mystring:String = "indeep";
let findCharacter:Character = "d";

if (mystring.characters.contains(findCharacter))
{
    let position = mystring.characters.indexOf(findCharacter);
    NSLog("Position of c is \(mystring.startIndex.distanceTo(position!))")

}
else
{
    NSLog("Position of c is not found");
}
En profondeur
la source
0

Je joue avec les suivants

extension String {
    func allCharactes() -> [Character] {
         var result: [Character] = []
         for c in self.characters {
             result.append(c)
         }
         return 
    }
}

jusqu'à ce que je comprenne celui fourni maintenant c'est juste un tableau de caractères

et avec

let c = Array(str.characters)
Peter Ahlberg
la source
Qu'est-ce que cela accomplit? Comment votre premier bloc de code est-il meilleur que le second?
Ben Leggiero
0

Si vous n'avez besoin que de l'index d'un caractère, la solution la plus simple et rapide (comme déjà souligné par Pascal) est:

let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)
Markymark
la source
les caractères sont dépréciés maintenant ... malheureusement, même si cela fonctionne
user3069232
0

Au sujet de transformer un String.Indexen un Int, cette extension fonctionne pour moi:

public extension Int {
    /// Creates an `Int` from a given index in a given string
    ///
    /// - Parameters:
    ///    - index:  The index to convert to an `Int`
    ///    - string: The string from which `index` came
    init(_ index: String.Index, in string: String) {
        self.init(string.distance(from: string.startIndex, to: index))
    }
}

Exemple d'utilisation correspondant à cette question:

var testString = "abcdefg"

Int(testString.range(of: "c")!.lowerBound, in: testString)     // 2

testString = "🇨🇦🇺🇸🇩🇪👩‍👩‍👧‍👦\u{1112}\u{1161}\u{11AB}"

Int(testString.range(of: "🇨🇦🇺🇸🇩🇪")!.lowerBound, in: testString) // 0
Int(testString.range(of: "👩‍👩‍👧‍👦")!.lowerBound, in: testString)    // 1
Int(testString.range(of: "한")!.lowerBound, in: testString)    // 5

Important:

Comme vous pouvez le constater, il regroupe les grappes de graphèmes étendues et les caractères joints différemment de String.Index. Bien sûr, c'est pourquoi nous l'avons fait String.Index. Vous devez garder à l'esprit que cette méthode considère les clusters comme des caractères singuliers, ce qui est plus proche de la correction. Si votre objectif est de diviser une chaîne par point de code Unicode, ce n'est pas la solution pour vous.

Ben Leggiero
la source
0

Dans Swift 2.0 , la fonction suivante renvoie une sous-chaîne avant un caractère donné.

func substring(before sub: String) -> String {
    if let range = self.rangeOfString(sub),
        let index: Int = self.startIndex.distanceTo(range.startIndex) {
        return sub_range(0, index)
    }
    return ""
}
superarts.org
la source
0

Vous pouvez trouver le numéro d'index d'un caractère dans une chaîne avec ceci:

var str = "abcdefghi"
if let index = str.firstIndex(of: "c") {
    let distance = str.distance(from: str.startIndex, to: index)
    // distance is 2
}
Ale Mohamad
la source
0

Comme mon point de vue, la meilleure façon de connaître la logique elle-même est ci-dessous

 let testStr: String = "I love my family if you Love us to tell us I'm with you"
 var newStr = ""
 let char:Character = "i"

 for value in testStr {
      if value == char {
         newStr = newStr + String(value)
   }

}
print(newStr.count)
Deepak Yadeedya
la source