web-dev-qa-db-fra.com

Index d'une sous-chaîne dans une chaîne avec Swift

Je suis habitué à le faire en JavaScript:

var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"

Swift n'a pas cette fonction, comment faire quelque chose de similaire?

43
Armand Grillet

Xcode 9 • Swift 4 ou version ultérieure

extension StringProtocol where Index == String.Index {
    func index(of string: Self, options: String.CompareOptions = []) -> Index? {
        return range(of: string, options: options)?.lowerBound
    }
    func endIndex(of string: Self, options: String.CompareOptions = []) -> Index? {
        return range(of: string, options: options)?.upperBound
    }
    func indexes(of string: Self, options: String.CompareOptions = []) -> [Index] {
        var result: [Index] = []
        var start = startIndex
        while start < endIndex,
            let range = self[start..<endIndex].range(of: string, options: options) {
            result.append(range.lowerBound)
            start = range.lowerBound < range.upperBound ? range.upperBound :
                    index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
        }
        return result
    }
    func ranges(of string: Self, options: String.CompareOptions = []) -> [Range<Index>] {
        var result: [Range<Index>] = []
        var start = startIndex
        while start < endIndex,
            let range = self[start..<endIndex].range(of: string, options: options) {
            result.append(range)
            start = range.lowerBound < range.upperBound ? range.upperBound :
                    index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
        }
        return result
    }
}

utilisation:

let str = "abcde"
if let index = str.index(of: "cd") {
    let substring = str[..<index]   // ab
    let string = String(substring)
    print(string)  // "ab\n"
}

let str = "Hello, playground, playground, playground"
str.index(of: "play")      // 7
str.endIndex(of: "play")   // 11
str.indexes(of: "play")    // [7, 19, 31]
str.ranges(of: "play")     // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]

échantillon insensible à la casse

let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] }   //
print(matches)  // ["play", "play", "play"]

échantillon d'expression régulière

let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "\\b\(escapedQuery)\\w+"  // matches any Word that starts with "play" prefix

let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }

print(matches) //  ["playground", "playground", "playground"]
81
Leo Dabus

Testé pour Swift 4.2/4.1/4.0/3.0

En utilisantString[Range<String.Index>]subscript, vous pouvez obtenir la sous-chaîne. Vous avez besoin de l'index de départ et du dernier index pour créer la plage et vous pouvez le faire comme ci-dessous

let str = "abcde"
if let range = str.range(of: "cd") {
  let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound]
  print(substring)  // Prints ab
}
else {
  print("String not present")
}

Si vous ne définissez pas l'index de démarrage de cet opérateur ..<, il utilise l'index de départ. Vous pouvez également utiliser str[str.startIndex..<range.lowerBound] au lieu de str[..<range.lowerBound]

31

Dans Swift 4:

Obtenir l'index d'un caractère dans une chaîne:

let str = "abcdefghabcd"
if let index = str.index(of: "b") {
   print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}

Création de sous-chaînes (préfixes et suffixes) à partir de String avec Swift 4:

let str : String = "ilike"
for i in 0...str.count {
    let index = str.index(str.startIndex, offsetBy: i) // String.Index
    let prefix = str[..<index] // String.SubSequence
    let suffix = str[index...] // String.SubSequence
    print("prefix \(prefix), suffix : \(suffix)")
}

Sortie

prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix : 

Si vous souhaitez générer une sous-chaîne entre 2 index, utilisez:

let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
5
Ashis Laha

Faire cela dans Swift est possible mais cela prend plus de lignes, voici une fonction indexOf() faisant ce qui est attendu

func indexOf(source: String, substring: String) -> Int? {
    let maxIndex = source.characters.count - substring.characters.count
    for index in 0...maxIndex {
        let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count)
        if source.substringWithRange(rangeSubstring) == substring {
            return index
        }
    }
    return nil
}

var str = "abcde"
if let indexOfCD = indexOf(str, substring: "cd") {
    let distance = str.startIndex.advancedBy(indexOfCD)
    print(str.substringToIndex(distance)) // Returns "ab"
}

Cette fonction n'est pas optimisée, mais elle fonctionne pour les chaînes courtes.

3
Armand Grillet

Dans la version 3 de Swift, String n’a pas les fonctions suivantes:

str.index(of: String)

Si l'index est requis pour une sous-chaîne, l'un des moyens consiste à obtenir la plage. Nous avons les fonctions suivantes dans la chaîne qui renvoie la plage - 

str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)

Par exemple pour trouver les index de première apparition de jeu dans str

var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4

Remarque: la plage est facultative. S'il n'est pas capable de trouver la chaîne, il sera rendu nul. Par exemple

var str = "play play play"
var range = str.range(of: "Zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
2
Abhinav Arora

Il y a trois problèmes étroitement liés ici:

  • Toutes les méthodes de recherche de sous-chaîne sont terminées dans le monde Cocoa NSString (Foundation)

  • Foundation NSRange ne correspond pas à Swift Range; la première utilise le début et la longueur, la dernière utilise les extrémités

  • En général, les caractères Swift sont indexés en utilisant String.Index, pas Int, mais les caractères Foundation sont indexés en utilisant Int, et il n'y a pas de traduction directe simple entre eux (car Foundation et Swift ont des conceptions différentes de ce qui constitue un caractère)

Compte tenu de tout cela, réfléchissons à la façon d'écrire:

func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
    // ?
}

La sous-chaîne s2 doit être recherchée dans s à l'aide d'une méthode String Foundation. La plage résultante nous revient, pas sous la forme d'une NSRange (même s'il s'agit d'une méthode Foundation), mais sous la forme d'une plage de String.Index (entourée d'une option, au cas où nous n'aurions pas trouvé la sous-chaîne du tout) . Cependant, l'autre nombre, from, est un Int. Ainsi, nous ne pouvons former aucune sorte de gamme impliquant les deux.

Mais nous n'avons pas à le faire! Tout ce que nous avons à faire est de couper le end de notre chaîne originale en utilisant une méthode qui prend un String.Index, et de couper le début de notre chaîne originale en utilisant une méthode qui prend un Int. Heureusement, de telles méthodes existent! Comme ça:

func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
    guard let r = s.range(of:s2) else {return nil}
    var s = s.prefix(upTo:r.lowerBound)
    s = s.dropFirst(from)
    return s
}

Ou, si vous préférez pouvoir appliquer cette méthode directement à une chaîne, comme ceci ...

let output = "abcde".substring(from:0, toSubstring:"cd")

... puis en faire une extension sur String:

extension String {
    func substring(from:Int, toSubstring s2 : String) -> Substring? {
        guard let r = self.range(of:s2) else {return nil}
        var s = self.prefix(upTo:r.lowerBound)
        s = s.dropFirst(from)
        return s
    }
}
0
matt