web-dev-qa-db-fra.com

Recherche d'un index de caractère dans Swift String

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 cela me donne juste un String.Index, que je peux utiliser pour retranscrire dans la chaîne d'origine, mais sans en extraire un emplacement.

FWIW, ce String.Index a un ivar privé appelé _position qui a la valeur correcte. Je ne vois tout simplement pas comment c'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.

192
Matt Wilding

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

String n'implémente pas RandomAccessIndexType. Probablement parce qu'ils activent des caractères avec des longueurs d'octets différentes. C'est pourquoi nous devons utiliser string.characters.count (count ou countElements dans Swift 1.x) pour obtenir le nombre de caractères. Cela s'applique également aux positions. Le _position est probablement un index dans le tableau d'octets brut et ils ne veulent pas l'exposer. Le String.Index est destiné à nous protéger de l'accès aux octets au milieu des caractères.

Cela signifie que tout index obtenu doit être créé à partir de String.startIndex ou String.endIndex (String.Index implémente BidirectionalIndexType). Tous les autres index peuvent être créés en utilisant les méthodes successor ou predecessor.

Maintenant, pour nous aider avec les index, 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.Index est fastidieux, mais utiliser un wrapper pour indexer par des entiers (voir https://stackoverflow.com/a/25152652/669586 ) est dangereux car il masque l'inefficacité de l'indexation réelle .

Notez que Swift La mise en oeuvre de l'indexation pose le problème suivant: les indices/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]  
237
Sulthan

Swift 3. rend cela un peu plus verbeux:

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. 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 pure Swift, 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 tant qu'extension de String:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = find(self, char) {
            return distance(self.startIndex, idx)
        }
        return nil
    }
}
86
Pascal
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
    }
}
23
huiquhome

J'ai trouvé cette solution pour Swift2:

var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2
16
VYT

Je ne sais pas comment extraire la position de String.Index, mais si vous êtes prêt à vous rabattre sur certains frameworks Objective-C, vous pouvez passer à objective-c et procéder de la même façon.

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

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

8
Connor

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
    }
}
8
YannSteph

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

var someString: NSString = "abcdefghi"

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

Je ne sais pas encore comment faire cela à Swift.

5
Logan

Vous pouvez également trouver les 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] c'est-à-dire. [Int], comme

"Apple".indexes(of: "p") // [1, 2]
"element".indexes(of: "e") // [0, 2, 4]
"Swift".indexes(of: "j") // []
5
Sandeep

Swift 5.

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

Swift 4.

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return index(of: char)?.encodedOffset        
  }
}
5
Vincenso

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

let loc = newString.range(of: ".").location
4
Some German Developer

Je sais que c'est vieux 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 chaînes Swift ici Strings in Swift

3
Jack

La manière la plus simple est:

Dans Swift:

 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)
2
Pragnesh Vitthani

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 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)
}
2
NatashaTheRobot

Le type de variable String dans Swift contient différentes fonctions 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 valable dans Swift). Cela vous donnera accès aux fonctions de NSString.

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

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);
2
rdelmar

En termes de réflexion, cela pourrait s'appeler une INVERSION. Vous découvrez que le monde est rond au lieu de plat. "Vous n'avez pas vraiment besoin de connaître l'INDEX du personnage pour en faire quelque chose." 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 dans une aire de jeux)

    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

Toutefois, si vous souhaitez 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, le décompte d'un caractère spécifique et une plage que ce code plug-in -able de terrain de jeu démontrera.

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!)
1
Mountain Man

Solution complète Swift 4:

OffsetIndexableCollection (Chaîne utilisant Int Index)

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
}
1
frogcjn

String est un type de pont pour NSString, ajoutez donc

import Cocoa

dans votre fichier Swift et utilisez toutes les "anciennes" méthodes.

1
IgnazioC

Si vous cherchez un moyen simple d’obtenir l’index de caractère ou de chaîne, consultez cette bibliothèque http://www.dollarswift.org/#indexof-char-character-int

Vous pouvez obtenir l'indexOf d'une chaîne en utilisant une autre chaîne ou un motif regex

0
Encore PTL
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");
}
0
Indeep

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

}

0
Uddiptta Ujjawal

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")
}
0
Yoosaf Abdulla

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) 
   ...
}
0
Tony

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

let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)
0
Markymark

Dans Swift 2., 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 ""
}
0
superarts.org

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
}
0
Ale Mohamad

Je joue avec suivant

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, il est juste tableau de caractères

et avec

let c = Array(str.characters)
0
Peter Ahlberg

Pour transformer un String.Index en 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 pertinent pour 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 avons String.Index. N'oubliez pas 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 scinder une chaîne par un code Unicode, ce n'est pas la solution pour vous.

0
Ben Leggiero