Comment convertir «Index» en type «Int» dans Swift?

91

Je souhaite convertir l'index d'une lettre contenue dans une chaîne en une valeur entière. Tentative de lecture des fichiers d'en-tête mais je ne trouve pas le type pour Index, bien qu'il semble conforme au protocole ForwardIndexTypeavec des méthodes (par exemple distanceTo).

var letters = "abcdefg"
let index = letters.characters.indexOf("c")!

// ERROR: Cannot invoke initializer for type 'Int' with an argument list of type '(String.CharacterView.Index)'
let intValue = Int(index)  // I want the integer value of the index (e.g. 2)

Toute aide est appréciée.

Christophe
la source
avez-vous xcode 7.2 et swift 2.x?
aaisataev
En fait, je télécharge Xcode 7.2 en ce moment.
Christopher
33
Il n'y a rien de plus frustrant que de voir l'index que vous voulez vous regarder en face dans une aire de jeux et c'est un gigantesque PITA pour convertir l'index au type dont vous avez besoin. Je préfère claquer mon pecker dans une porte.
Adrian
1
Swift 3: let index = letters.characters.index(of: "c") Next Linelet int_index = letters.characters.distance(from: letters.startIndex, to: index)
Viktor
8
Apple WTF !!!!!!
Borzh

Réponses:

87

modifier / mettre à jour:

Xcode 11 • Swift 5.1 ou version ultérieure

extension StringProtocol {
    func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
    func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}

extension Collection {
    func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}

extension String.Index {
    func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}

Test de terrain de jeu

let letters = "abcdefg"

let char: Character = "c"
if let distance = letters.distance(of: char) {
    print("character \(char) was found at position #\(distance)")   // "character c was found at position #2\n"
} else {
    print("character \(char) was not found")
}

let string = "cde"
if let distance = letters.distance(of: string) {
    print("string \(string) was found at position #\(distance)")   // "string cde was found at position #2\n"
} else {
    print("string \(string) was not found")
}
Leo Dabus
la source
48
Je ne sais trop pourquoi ils ne décideraient pas simplement de créer une fonction qui renvoie l'index de valeur entière de l'élément d'un tableau. Smh
MarksCode
6
Tous les caractères ne peuvent pas être stockés dans un seul octet. Vous devriez prendre un peu de temps et lire la documentation de Swift String
Leo Dabus
4
Tbh J'essaye d'obtenir l'index de valeur entière de l'élément d'un tableau régulier, pas une chaîne.
MarksCode
28
Rendre les choses simples inutilement compliquées :(
Iulian Onofrei
4

Swift 4

var str = "abcdefg"
let index = str.index(of: "c")?.encodedOffset // Result: 2

Remarque: si String contient les mêmes caractères multiples, il obtiendra simplement le plus proche de la gauche

var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Shem Alexis Chavez
la source
6
N'utilisez pas encodedOffset. encodedOffset est obsolète: encodedOffset est obsolète car l'utilisation la plus courante est incorrecte. essayer"🇺🇸🇺🇸🇧🇷".index(of: "🇧🇷")?.encodedOffset // 16
Leo Dabus
@LeoDabus vous déclarez correctement qu'il est obsolète. Mais vous suggérez quand même de l'utiliser comme réponse. La bonne réponse est: index_Of_YourStringVariable.utf16Offset (in: yourStringVariable)
TDesign
Non, le décalage UTF16 n'est probablement pas ce que vous voulez
Leo Dabus
1

encodedOffsetest obsolète depuis Swift 4.2 .

Message d'abandon: encodedOffsetest obsolète car l'utilisation la plus courante est incorrecte. Utilisez utf16Offset(in:)pour obtenir le même comportement.

Nous pouvons donc utiliser utf16Offset(in:)comme ceci:

var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
Shohin
la source
1
essayer let str = "🇺🇸🇺🇸🇧🇷" let index = str.firstIndex(of: "🇧🇷")?.utf16Offset(in: str)// Résultat: 8
Leo Dabus
0

Pour effectuer une opération de chaîne basée sur un index, vous ne pouvez pas le faire avec l'approche numérique traditionnelle d'index. car swift.index est récupéré par la fonction index et n'est pas de type Int. Même si String est un tableau de caractères, nous ne pouvons toujours pas lire élément par index.

C'est frustrant.

Donc, pour créer une nouvelle sous-chaîne de chaque caractère pair de chaîne, vérifiez le code ci-dessous.

let mystr = "abcdefghijklmnopqrstuvwxyz"
let mystrArray = Array(mystr)
let strLength = mystrArray.count
var resultStrArray : [Character] = []
var i = 0
while i < strLength {
    if i % 2 == 0 {
        resultStrArray.append(mystrArray[i])
      }
    i += 1
}
let resultString = String(resultStrArray)
print(resultString)

Sortie: acegikmoqsuwy

Merci d'avance

Shrikant Phadke
la source
cela peut être facilement accompli avec un filtrevar counter = 0 let result = mystr.filter { _ in defer { counter += 1 } return counter.isMultiple(of: 2) }
Leo Dabus
si vous préférez utiliser le String.indexvar index = mystr.startIndex let result = mystr.filter { _ in defer { mystr.formIndex(after: &index) } return mystr.distance(from: mystr.startIndex, to: index).isMultiple(of: 2) }
Leo Dabus
0

Voici une extension qui vous permettra d'accéder aux limites d'une sous-chaîne en tant que Ints au lieu de String.Indexvaleurs:

import Foundation

/// This extension is available at
/// https://gist.github.com/zackdotcomputer/9d83f4d48af7127cd0bea427b4d6d61b
extension StringProtocol {
    /// Access the range of the search string as integer indices
    /// in the rendered string.
    /// - NOTE: This is "unsafe" because it may not return what you expect if
    ///     your string contains single symbols formed from multiple scalars.
    /// - Returns: A `CountableRange<Int>` that will align with the Swift String.Index
    ///     from the result of the standard function range(of:).
    func countableRange<SearchType: StringProtocol>(
        of search: SearchType,
        options: String.CompareOptions = [],
        range: Range<String.Index>? = nil,
        locale: Locale? = nil
    ) -> CountableRange<Int>? {
        guard let trueRange = self.range(of: search, options: options, range: range, locale: locale) else {
            return nil
        }

        let intStart = self.distance(from: startIndex, to: trueRange.lowerBound)
        let intEnd = self.distance(from: trueRange.lowerBound, to: trueRange.upperBound) + intStart

        return Range(uncheckedBounds: (lower: intStart, upper: intEnd))
    }
}

Sachez simplement que cela peut conduire à l'étrangeté, c'est pourquoi Apple a choisi de rendre les choses difficiles. (Bien que ce soit une décision de conception discutable - cacher une chose dangereuse en la rendant juste difficile ...)

Vous pouvez en lire plus dans la documentation String d'Apple , mais le tldr est qu'il provient du fait que ces "indices" sont en fait spécifiques à l'implémentation. Ils représentent les indices dans la chaîne après son rendu par le système d'exploitation et peuvent donc passer d'un système d'exploitation à un autre en fonction de la version de la spécification Unicode utilisée. Cela signifie que l'accès aux valeurs par index n'est plus une opération à temps constant, car la spécification UTF doit être exécutée sur les données pour déterminer le bon endroit dans la chaîne. Ces indices ne s'aligneront pas non plus avec les valeurs générées par NSString, si vous y faites un pont, ou avec les indices dans les scalaires UTF sous-jacents. Développeur de mise en garde.

Zack
la source
pas besoin de mesurer à nouveau la distance du startIndex. Obtenez simplement la distance entre le bas et le haut et ajoutez le début.
Leo Dabus
J'ajouterais également les options à votre méthode. Quelque chose commefunc rangeInt<S: StringProtocol>(of aString: S, options: String.CompareOptions = []) -> Range<Int>? { guard let range = range(of: aString, options: options) else { return nil } let start = distance(from: startIndex, to: range.lowerBound) return start..<start+distance(from: range.lowerBound, to: range.upperBound) }
Leo Dabus
Je changerais probablement le nom de la méthode countableRangeet reviendraisCountableRange
Leo Dabus
Bonnes suggestions @LeoDabus à tous les niveaux. J'ai ajouté tous les arguments range (of ...), en fait, donc vous pouvez maintenant appeler countableRange (of :, options :, range:, locale :). J'ai mis à jour le Gist, mettra également à jour ce message.
Zack le
Je ne pense pas que la plage soit nécessaire étant donné que vous pouvez appeler cette méthode sur une sous
Leo Dabus