web-dev-qa-db-fra.com

Comment fonctionne la sous-chaîne de caractères dans Swift

J'ai mis à jour une partie de mon ancien code et des réponses avec Swift 3, mais lorsque je suis arrivé à Swift Strings et à l'indexation avec des sous-chaînes, les choses sont devenues confuses. 

Plus précisément, j'essayais ce qui suit:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

où la deuxième ligne me donnait l'erreur suivante

La valeur de type 'String' n'a pas de membre 'substringWithRange'

Je vois que String a maintenant les méthodes suivantes:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

C’était vraiment déroutant au début, j’ai donc commencé à jouer avec index et range . Ceci est une question de suivi et une réponse pour la sous-chaîne. J'ajoute une réponse ci-dessous pour montrer comment ils sont utilisés.

270
Suragch

 enter image description here

Tous les exemples suivants utilisent 

var str = "Hello, playground"

Swift 4

Les chaînes ont subi une refonte considérable dans Swift 4. Lorsque vous récupérez une sous-chaîne à partir d'une chaîne, vous obtenez un type Substring au lieu d'un String. Pourquoi est-ce? Les chaînes sont des types de valeur dans Swift. Cela signifie que si vous utilisez une chaîne pour en créer une nouvelle, elle doit être copiée. C'est bon pour la stabilité (personne d'autre ne le changera à votre insu) mais mauvais pour l'efficacité. 

Une sous-chaîne, en revanche, est une référence à la chaîne d'origine dont elle provient. Voici une image de la documentation illustrant cela.

Aucune copie n'est nécessaire, son utilisation est donc beaucoup plus efficace. Cependant, imaginez que vous ayez une sous-chaîne de dix caractères parmi un million de caractères. Étant donné que la sous-chaîne fait référence à la chaîne, le système devrait conserver toute la chaîne tant que la sous-chaîne est présente. Ainsi, chaque fois que vous avez fini de manipuler votre sous-chaîne, convertissez-la en chaîne.

let myString = String(mySubstring)

Cela ne fera que copier la sous-chaîne et l'ancienne chaîne pourra être nettoyée. Les sous-chaînes (en tant que type) sont censées être de courte durée.

Une autre grande amélioration dans Swift 4 est que les chaînes sont des collections (à nouveau). Cela signifie que tout ce que vous pouvez faire sur une collection, vous pouvez le faire sur une chaîne (utilisez des indices, parcourez les caractères, filtrez, etc.). 

Les exemples suivants montrent comment obtenir une sous-chaîne dans Swift.

Obtenir des sous-chaînes

Vous pouvez obtenir une sous-chaîne à partir d'une chaîne en utilisant des indices ou un certain nombre d'autres méthodes (par exemple, prefix, suffix, split). Vous devez néanmoins utiliser String.Index et non un index Int pour la plage. (Voir mon autre réponse si vous avez besoin d'aide pour cela.)

Début d'une ficelle

Vous pouvez utiliser un indice (notez la plage unilatérale de Swift 4):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

ou prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

ou encore plus facile:

let mySubstring = str.prefix(5) // Hello

Fin d'une ficelle

Utilisation des indices:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

ou suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

ou encore plus facile:

let mySubstring = str.suffix(10) // playground

Notez que lorsque vous utilisez suffix(from: index), je devais compter à partir de la fin en utilisant -10. Cela n'est pas nécessaire si vous utilisez simplement suffix(x), qui prend simplement les derniers caractères x d'une chaîne.

Range dans une chaîne

Encore une fois, nous utilisons simplement des indices ici.

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range]  // play

Conversion de Substring en String

N'oubliez pas que lorsque vous êtes prêt à enregistrer votre sous-chaîne, vous devez la convertir en String afin que la mémoire de l'ancienne chaîne puisse être nettoyée.

let myString = String(mySubstring)

Vous utilisez une extension d'index Int?

J'hésite à utiliser une extension d'index basée sur Int après avoir lu l'article Strings in Swift 3 de Airspeed Velocity et Ole Begemann. Bien que dans Swift 4, les chaînes soient des collections, l’équipe Swift n’a volontairement pas utilisé les index Int. C'est toujours String.Index. Cela concerne les caractères rapides composés d'un nombre variable de points de code Unicode. L'indice réel doit être calculé de manière unique pour chaque chaîne. 

Je dois dire que j'espère que l'équipe de Swift trouvera un moyen d'abstraire String.Index à l'avenir. Mais jusqu'à ce que je choisis d'utiliser leur API. Cela m'aide à me rappeler que les manipulations de chaîne ne sont pas simplement de simples recherches d'index Int

660
Suragch

Je suis vraiment frustré par le modèle d'accès par chaîne de Swift: tout doit être une Index. Tout ce que je veux, c'est accéder au i-ème caractère de la chaîne en utilisant Int, et non à l'index maladroit et à l'avancement (ce qui change parfois à chaque version majeure). J'ai donc fait une extension à String:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return substring(from: fromIndex)
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return substring(to: toIndex)
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return substring(with: startIndex..<endIndex)
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play
150
Code Different

Swift 4 Extension:

extension String { 
    subscript(_ range: CountableRange<Int>) -> String { 
        let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
        let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
        return String(self[idx1..<idx2])
    }    
}       

Usage: 

let s = "hello"
s[0..<3] // "hel"
s[3..<s.count] // "lo"

Ou unicode:

let s = "????????????"
s[0..<1] // "????"
52
Lou Zell

Swift 4

Dans Swift 4, String est conforme à Collection. Au lieu de substring, nous devrions maintenant utiliser un subscript. Ainsi, si vous voulez supprimer uniquement le mot "play" de "Hello, playground", vous pouvez le faire comme ceci:

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

Il est intéressant de savoir que cela vous donnera une Substring au lieu de String. Ceci est rapide et efficace puisque Substring partage son stockage avec la chaîne d'origine. Cependant, le partage de la mémoire de cette manière peut également facilement entraîner des fuites de mémoire.

C'est pourquoi vous devez copier le résultat dans une nouvelle chaîne, une fois que vous souhaitez nettoyer la chaîne d'origine. Vous pouvez le faire en utilisant le constructeur normal:

let newString = String(result)

Vous trouverez plus d’informations sur la nouvelle classe Substring dans la [documentation Apple] . 1

Ainsi, si vous obtenez par exemple une Range à la suite d'une NSRegularExpression, vous pouvez utiliser l'extension suivante:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}
19
gebirgsbärbel

Swift 4 & 5:

extension String {
  subscript(_ i: Int) -> String {
    let idx1 = index(startIndex, offsetBy: i)
    let idx2 = index(idx1, offsetBy: 1)
    return String(self[idx1..<idx2])
  }

  subscript (r: Range<Int>) -> String {
    let start = index(startIndex, offsetBy: r.lowerBound)
    let end = index(startIndex, offsetBy: r.upperBound)
    return String(self[start ..< end])
  }

  subscript (r: CountableClosedRange<Int>) -> String {
    let startIndex =  self.index(self.startIndex, offsetBy: r.lowerBound)
    let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
    return String(self[startIndex...endIndex])
  }
}

Comment l'utiliser:

"abcde" [0] -> "a"

"abcde" [0 ... 2] -> "abc"

"abcde" [2 .. <4] -> "cd"

8
Souf ROCHDI

J'ai eu la même réaction initiale. Moi aussi, j'étais frustré par la façon dont la syntaxe et les objets changent de manière si radicale dans chaque version majeure.

Cependant, j’ai réalisé par expérience que je finissais toujours par subir les conséquences d’essayer de lutter contre le "changement", comme traiter avec des caractères multi-octets, ce qui est inévitable si vous recherchez un public mondial.

J'ai donc décidé de reconnaître et de respecter les efforts déployés par les ingénieurs Apple et de faire ma part en comprenant leur état d'esprit lorsqu'ils ont proposé cette approche "horrible".

Au lieu de créer des extensions qui ne sont qu'une solution de contournement pour vous simplifier la vie (je ne dis pas qu'elles sont fausses ou coûteuses), pourquoi ne pas comprendre comment les chaînes sont maintenant conçues pour fonctionner.

Par exemple, j'avais ce code qui travaillait sur Swift 2.2:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

et après avoir renoncé à essayer de faire fonctionner la même approche, par exemple. en utilisant Substrings, j'ai finalement compris le concept de traiter Strings comme une collection bidirectionnelle pour laquelle je me suis retrouvé avec cette version du même code:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

J'espère que cela contribue ...

7
Rio Bautista

Voici une fonction qui retourne une sous-chaîne d'une sous-chaîne donnée lorsque des index de début et de fin sont fournis. Pour une référence complète, vous pouvez visiter les liens ci-dessous.

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for Swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

Voici un lien vers l'article de blog que j'ai créé pour traiter la manipulation de chaînes dans Swift. Manipulation des chaînes dans Swift (couvre également Swift 4)

Ou vous pouvez voir ce Gist sur github

7
Nikesh Jha

Je suis nouveau dans Swift 3, mais en cherchant dans l'analogie la syntaxe String (index), je pense que l'index est comme un "pointeur" contraint par une chaîne et que Int peut aider en tant qu'objet indépendant. En utilisant la syntaxe base + offset, nous pouvons alors obtenir le i-ème caractère de chaîne avec le code ci-dessous: 

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

Pour une plage de caractères (index) de chaîne utilisant la syntaxe String (plage), nous pouvons obtenir les i-ième à f-ième caractères avec le code ci-dessous:

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

Pour une sous-chaîne (plage) d'une chaîne utilisant String.substring (plage), nous pouvons obtenir la sous-chaîne en utilisant le code ci-dessous:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

Remarques: 

  1. Les i-ème et f-ème commencent par 0.

  2. Pour f-th, j'utilise offsetBY: f + 1, car la plage d'abonnement utilise .. <(opérateur semi-ouvert), n'inclut pas la f-ème position.

  3. Bien sûr, il faut inclure les erreurs de validation telles que l'index invalide.

5
Nelson Mizutani

Même frustration, ça ne devrait pas être si dur ...

J'ai compilé cet exemple d'obtention de positions pour une sous-chaîne à partir d'un texte plus grand:

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

renvoie .__ ("Pourquoi", 0, 3) .__ ("sous-chaînes", 26, 36).

4
Tall Dane

Swift 4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o

J'ai créé une extension simple pour cela (Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}
1
Lucas Algarra

En m'appuyant sur ce qui précède, je devais scinder une chaîne sur un caractère non imprimable en supprimant ce dernier. J'ai développé deux méthodes:

var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]

que je mets ensemble en utilisant certaines des réponses ci-dessus.

Puisqu'une chaîne est une collection, j'ai ensuite fait ce qui suit:

var fString = String()
for (n,c) in str.enumerated(){

*if c == "\u{1A}" {
    print(fString);
    let lString = str.dropFirst(n + 1)
    print(lString)
    break
   }
 fString += String(c)
}*

Ce qui pour moi était plus intuitif. Lequel est le meilleur? Je n'ai aucun moyen de dire qu'ils travaillent tous les deux avec Swift 5

1
Jeremy Andrews

Je pense assez mécaniquement. Voici les bases ...

Swift 4 Swift 5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

Le résultat est une sous-chaîne, ce qui signifie qu'il fait partie de la chaîne d'origine. Pour obtenir une chaîne séparée complète, utilisez par exemple, par exemple.

    String(t3)
    String(t4)

C'est ce que j'utilise:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]
0
t1ser

Swift 4+

extension String {
    func take(_ n: Int) -> String {
        guard n >= 0 else {
            fatalError("n should never negative")
        }
        let index = self.index(self.startIndex, offsetBy: min(n, self.count))
        return String(self[..<index])
    }
}

Retourne une sous-séquence des n premiers caractères, ou la chaîne entière si la chaîne est plus courte. (inspiré par: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html )

Exemple:

let text = "Hello, World!"
let substring = text.take(5) //Hello
0
Peter Kreinz

Swift 4

"Sous-chaîne" ( https://developer.Apple.com/documentation/Swift/substring ):

let greeting = "Hi there! It's Nice to meet you! ????"
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

Exemple d'extension String:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

exemple d'utilisation de l'extension String:

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"
0
CAHbl463

Voici une implémentation plus générique:

Cette technique utilise toujours index pour respecter les normes de Swift et implique un caractère complet.

extension String
{
    func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
    {
        return String(self[range])
    }

    func index(at: Int) -> Index
    {
        return self.index(self.startIndex, offsetBy: at)
    }
}

Pour sous-chaîne du 3ème caractère:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

J'ai utilisé chamel subString pour indiquer qu'il renvoie une String et non une Substring.

0
Leslie Godwin