Comment créer une gamme dans Swift?


104

En Objective-c, nous créons une plage en utilisant NSRange

NSRange range;

Alors, comment créer une gamme dans Swift?


3
Pourquoi avez-vous besoin de la gamme? Les chaînes Swift utilisent Range<String.Index>, mais parfois il est nécessaire de travailler avec NSStringet NSRange, donc un peu plus de contexte serait utile. - Mais jetez un œil à stackoverflow.com/questions/24092884/… .
Martin R

Réponses:


258

Mis à jour pour Swift 4

Les gammes Swift sont plus complexes que NSRange, et elles ne sont pas devenues plus faciles dans Swift 3. Si vous voulez essayer de comprendre le raisonnement derrière une partie de cette complexité, lisez ceci et ceci . Je vais juste vous montrer comment les créer et quand vous pourriez les utiliser.

Plages fermées: a...b

Cet opérateur de plage crée une plage Swift qui comprend à la fois un élément a et un élément b, même s'il bs'agit de la valeur maximale possible pour un type (comme Int.max). Il existe deux types différents de plages fermées: ClosedRangeet CountableClosedRange.

1. ClosedRange

Les éléments de toutes les plages de Swift sont comparables (c'est-à-dire qu'ils sont conformes au protocole Comparable). Cela vous permet d'accéder aux éléments de la plage à partir d'une collection. Voici un exemple:

let myRange: ClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

Cependant, a ClosedRangen'est pas dénombrable (c'est-à-dire qu'il n'est pas conforme au protocole Sequence). Cela signifie que vous ne pouvez pas parcourir les éléments avec une forboucle. Pour cela, vous avez besoin du CountableClosedRange.

2. CountableClosedRange

Ceci est similaire au dernier sauf que maintenant la plage peut également être itérée.

let myRange: CountableClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

for index in myRange {
    print(myArray[index])
}

Gammes semi-ouvertes: a..<b

Cet opérateur de plage inclut un élément amais pas un élément b. Comme ci-dessus, il existe deux types différents de plages semi-ouvertes: Rangeet CountableRange.

1. Range

Comme avec ClosedRange, vous pouvez accéder aux éléments d'une collection avec un Range. Exemple:

let myRange: Range = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

Encore une fois, cependant, vous ne pouvez pas itérer sur un Range car il est seulement comparable, non stridable.

2. CountableRange

A CountableRangepermet l'itération.

let myRange: CountableRange = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

for index in myRange {
    print(myArray[index])
}

NSRange

Vous pouvez (devez) toujours l'utiliser NSRangeà certains moments dans Swift (lors de la création de chaînes attribuées , par exemple), il est donc utile de savoir comment en créer une.

let myNSRange = NSRange(location: 3, length: 2)

Notez qu'il s'agit de l'emplacement et de la longueur , et non de l'index de début et de l'index de fin. L'exemple ici est similaire dans sa signification à la gamme Swift 3..<5. Cependant, comme les types sont différents, ils ne sont pas interchangeables.

Gammes avec cordes

Les opérateurs de plage ...et ..<sont un moyen abrégé de créer des plages. Par exemple:

let myRange = 1..<3

Le moyen le plus long de créer la même gamme serait

let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3

Vous pouvez voir que le type d'index ici est Int. Cela ne fonctionne pas pour String, cependant, car les chaînes sont constituées de caractères et tous les caractères n'ont pas la même taille. (Lisez ceci pour plus d'informations.) Un emoji comme 😀, par exemple, prend plus d'espace que la lettre «b».

Problème avec NSRange

Essayez d'expérimenter avec NSRangeet un NSStringavec emoji et vous verrez ce que je veux dire. Mal de crâne.

let myNSRange = NSRange(location: 1, length: 3)

let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"

let myNSString2: NSString = "a😀cde"
myNSString2.substring(with: myNSRange) // "😀c"    Where is the "d"!?

Le visage souriant prend deux unités de code UTF-16 pour stocker, donc il donne le résultat inattendu de ne pas inclure le "d".

Solution rapide

Pour cette raison, avec Swift Strings que vous utilisez Range<String.Index>, non Range<Int>. L'index de chaîne est calculé en fonction d'une chaîne particulière afin qu'il sache s'il existe des emoji ou des grappes de graphèmes étendus.

Exemple

var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[myRange] // "bcd"

myString = "a😀cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "😀cd"

Gammes unilatérales: a...et...b et..<b

Dans Swift 4, les choses ont été un peu simplifiées. Chaque fois que le point de départ ou de fin d'une plage peut être déduit, vous pouvez le laisser désactivé.

Int

Vous pouvez utiliser des plages d'entiers à un côté pour parcourir les collections. Voici quelques exemples tirés de la documentation .

// iterate from index 2 to the end of the array
for name in names[2...] {
    print(name)
}

// iterate from the beginning of the array to index 2
for name in names[...2] {
    print(name)
}

// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
    print(name)
}

// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

// You can iterate over this but it will be an infinate loop 
// so you have to break out at some point.
let range = 5...

Chaîne

Cela fonctionne également avec les plages String. Si vous créez une plage avec str.startIndexou str.endIndexà une extrémité, vous pouvez la laisser désactivée. Le compilateur l'inférera.

Donné

var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)

let myRange = ..<index    // Hello

Vous pouvez passer de l'index à str.endIndex en utilisant ...

var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index...        // playground

Voir également:

Remarques

  • Vous ne pouvez pas utiliser une plage que vous avez créée avec une chaîne sur une chaîne différente.
  • Comme vous pouvez le voir, les plages de chaînes sont une douleur dans Swift, mais elles permettent de mieux gérer les emoji et autres scalaires Unicode.

Une étude plus approfondie


Mon commentaire concerne la sous-section "Problème avec NSRange". NSStringstocke en interne ses caractères en encodage UTF-16. Un scalaire Unicode complet est de 21 bits. Le caractère de visage souriant ( U+1F600) ne peut pas être stocké dans une seule unité de code 16 bits, il est donc réparti sur 2. NSRangecomptes basés sur des unités de code 16 bits. Dans cet exemple, 3 unités de code ne représentent que 2 caractères
Code différent

25

Xcode 8 bêta 2 • Swift 3

let myString = "Hello World"
let myRange = myString.startIndex..<myString.index(myString.startIndex, offsetBy: 5)
let mySubString = myString.substring(with: myRange)   // Hello

Xcode 7 • Swift 2.0

let myString = "Hello World"
let myRange = Range<String.Index>(start: myString.startIndex, end: myString.startIndex.advancedBy(5))

let mySubString = myString.substringWithRange(myRange)   // Hello

ou simplement

let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange)   // Hello


2

Utilisez comme ça

var start = str.startIndex // Start at the string's start index
var end = advance(str.startIndex, 5) // Take start index and advance 5 characters forward
var range: Range<String.Index> = Range<String.Index>(start: start,end: end)

let firstFiveDigit =  str.substringWithRange(range)

print(firstFiveDigit)

Sortie: Bonjour


Je vois qu'ils ont le type de données "Range". Alors, comment puis-je l'utiliser?
vichhai

@vichhai Pouvez-vous en dire plus là où vous souhaitez utiliser?
Dharmbir Singh

Comme dans l'objectif c: gamme NSRange;
vichhai

1

Je trouve surprenant que, même dans Swift 4, il n'y ait toujours pas de moyen natif simple d'exprimer une plage String en utilisant Int. Les seules méthodes String qui vous permettent de fournir un Int comme moyen d'obtenir une sous-chaîne par plage sont prefixet suffix.

Il est utile d'avoir sous la main des utilitaires de conversion, afin que nous puissions parler comme NSRange lorsque nous parlons à une chaîne. Voici un utilitaire qui prend un emplacement et une longueur, tout comme NSRange, et renvoie un Range<String.Index>:

func range(_ start:Int, _ length:Int) -> Range<String.Index> {
    let i = self.index(start >= 0 ? self.startIndex : self.endIndex,
        offsetBy: start)
    let j = self.index(i, offsetBy: length)
    return i..<j
}

Par exemple, "hello".range(0,1)"est-ce que l' Range<String.Index>embrassement est le premier caractère de "hello". En prime, j'ai autorisé les emplacements négatifs: "hello".range(-1,1)"est Range<String.Index>le dernier personnage de "hello".

Il est également utile de convertir a Range<String.Index>en NSRange, pour les moments où vous devez parler à Cocoa (par exemple, pour traiter les plages d'attributs NSAttributedString). Swift 4 fournit un moyen natif de le faire:

let nsrange = NSRange(range, in:s) // where s is the string

On peut ainsi écrire un autre utilitaire où l'on passe directement d'un emplacement et d'une longueur String à un NSRange:

extension String {
    func nsRange(_ start:Int, _ length:Int) -> NSRange {
        return NSRange(self.range(start,length), in:self)
    }
}

1

Si quelqu'un veut créer un objet NSRange peut créer comme:

let range: NSRange = NSRange.init(location: 0, length: 5)

cela créera une plage avec la position 0 et la longueur 5


1
func replace(input: String, start: Int,lenght: Int, newChar: Character) -> String {
    var chars = Array(input.characters)

    for i in start...lenght {
        guard i < input.characters.count else{
            break
        }
        chars[i] = newChar
    }
    return String(chars)
}

0

J'ai créé l'extension suivante:

extension String {
    func substring(from from:Int, to:Int) -> String? {
        if from<to && from>=0 && to<self.characters.count {
            let rng = self.startIndex.advancedBy(from)..<self.startIndex.advancedBy(to)
            return self.substringWithRange(rng)
        } else {
            return nil
        }
    }
}

exemple d'utilisation:

print("abcde".substring(from: 1, to: 10)) //nil
print("abcde".substring(from: 2, to: 4))  //Optional("cd")
print("abcde".substring(from: 1, to: 0))  //nil
print("abcde".substring(from: 1, to: 1))  //nil
print("abcde".substring(from: -1, to: 1)) //nil

0

Vous pouvez utiliser comme ça

let nsRange = NSRange(location: someInt, length: someInt)

un péché

let myNSString = bigTOTPCode as NSString //12345678
let firstDigit = myNSString.substringWithRange(NSRange(location: 0, length: 1)) //1
let secondDigit = myNSString.substringWithRange(NSRange(location: 1, length: 1)) //2
let thirdDigit = myNSString.substringWithRange(NSRange(location: 2, length: 4)) //3456

0

Je veux faire ça:

print("Hello"[1...3])
// out: Error

Mais malheureusement, je ne peux pas écrire moi-même un indice parce que celui qui est détesté occupe l'espace de nom.

Nous pouvons faire ceci cependant:

print("Hello"[range: 1...3])
// out: ell 

Ajoutez simplement ceci à votre projet:

extension String {
    subscript(range: ClosedRange<Int>) -> String {
        get {
            let start = String.Index(utf16Offset: range.lowerBound, in: self)
            let end = String.Index(utf16Offset: range.upperBound, in: self)
            return String(self[start...end])
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.