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.
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]
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
}
}
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
}
}
J'ai trouvé cette solution pour Swift2:
var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2
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.
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
}
}
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.
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") // []
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
}
}
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
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
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)
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)
}
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
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);
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!)
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
}
String est un type de pont pour NSString, ajoutez donc
import Cocoa
dans votre fichier Swift et utilisez toutes les "anciennes" méthodes.
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
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");
}
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
}
}
}
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")
}
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)
...
}
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)
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 ""
}
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
}
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)
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
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.