Come creare una gamma in Swift?


104

In Objective-c creiamo la gamma utilizzando NSRange

NSRange range;

Quindi come creare un raggio in Swift?


3
A cosa ti serve la gamma? Le stringhe veloci usano Range<String.Index>, ma a volte è necessario lavorare con NSStringe NSRange, quindi un po 'più di contesto sarebbe utile. - Ma dai un'occhiata a stackoverflow.com/questions/24092884/… .
Martin R

Risposte:


258

Aggiornato per Swift 4

Gli intervalli di Swift sono più complessi di NSRange, e non sono diventati più facili in Swift 3. Se vuoi provare a capire il ragionamento dietro alcune di queste complessità, leggi questo e questo . Ti mostrerò solo come crearli e quando potresti usarli.

Intervalli chiusi: a...b

Questo operatore di intervallo crea un intervallo Swift che include sia l'elemento a che l' elemento b, anche se bè il valore massimo possibile per un tipo (come Int.max). Esistono due diversi tipi di intervalli chiusi: ClosedRangee CountableClosedRange.

1. ClosedRange

Gli elementi di tutti gli intervalli in Swift sono confrontabili (ovvero sono conformi al protocollo Comparable). Ciò consente di accedere agli elementi della gamma da una raccolta. Ecco un esempio:

let myRange: ClosedRange = 1...3

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

Tuttavia, a ClosedRangenon è numerabile (cioè, non è conforme al protocollo Sequence). Ciò significa che non puoi iterare sugli elementi con un forciclo. Per questo hai bisogno del file CountableClosedRange.

2. CountableClosedRange

Questo è simile all'ultimo tranne che ora l'intervallo può anche essere ripetuto.

let myRange: CountableClosedRange = 1...3

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

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

Intervalli semiaperti: a..<b

Questo operatore di intervallo include l'elemento ama non l' elemento b. Come sopra, ci sono due diversi tipi di intervalli semiaperti: Rangee CountableRange.

1. Range

Come con ClosedRange, puoi accedere agli elementi di una raccolta con un file Range. Esempio:

let myRange: Range = 1..<3

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

Di nuovo, però, non puoi iterare su a Rangeperché è solo comparabile, non stridabile.

2. CountableRange

A CountableRangeconsente l'iterazione.

let myRange: CountableRange = 1..<3

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

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

NSRange

A volte puoi (devi) usare ancora NSRangein Swift (quando crei stringhe con attributi , per esempio), quindi è utile sapere come crearne una.

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

Nota che questa è la posizione e la lunghezza , non l'indice iniziale e l'indice finale. L'esempio qui è simile nel significato alla gamma Swift 3..<5. Tuttavia, poiché i tipi sono diversi, non sono intercambiabili.

Intervalli con archi

Gli operatori di intervallo ...e ..<rappresentano un modo abbreviato per creare intervalli. Per esempio:

let myRange = 1..<3

La lunga strada per creare la stessa gamma sarebbe

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

Puoi vedere che il tipo di indice qui è Int. Ciò non funziona String, però, perché le stringhe sono fatte di caratteri e non tutti i caratteri hanno le stesse dimensioni. (Leggi questo per maggiori informazioni.) Un'emoji come 😀, ad esempio, occupa più spazio della lettera "b".

Problema con NSRange

Prova a sperimentare con NSRangee NSStringcon le emoji e vedrai cosa intendo. Mal di testa.

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"!?

La faccina sorridente richiede due unità di codice UTF-16 per essere memorizzata, quindi dà il risultato inaspettato di non includere la "d".

Soluzione rapida

Per questo motivo, con Swift Strings che usi Range<String.Index>, no Range<Int>. L'indice di stringa viene calcolato in base a una stringa particolare in modo che sappia se sono presenti emoji o cluster grafema estesi.

Esempio

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"

Intervalli unilaterali: a...e ...be..<b

In Swift 4 le cose sono state un po 'semplificate. Ogni volta che è possibile dedurre il punto iniziale o finale di un intervallo, è possibile tralasciarlo.

Int

È possibile utilizzare intervalli di numeri interi unilaterali per scorrere le raccolte. Di seguito sono riportati alcuni esempi dalla documentazione .

// 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...

Corda

Funziona anche con gli intervalli di stringhe. Se stai creando un intervallo con str.startIndexo str.endIndexad un'estremità, puoi lasciarlo spento. Il compilatore lo dedurrà.

Dato

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

let myRange = ..<index    // Hello

Puoi passare dall'indice a str.endIndex usando ...

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

Guarda anche:

Appunti

  • Non puoi utilizzare un intervallo creato con una stringa su una stringa diversa.
  • Come puoi vedere, gli intervalli di stringhe sono un problema in Swift, ma lo fanno forse per affrontare meglio le emoji e altri scalari Unicode.

Ulteriori studi


Il mio commento riguarda la sottosezione "Problema con NSRange". NSStringmemorizza internamente i suoi caratteri nella codifica UTF-16. Uno scalare Unicode completo è a 21 bit. Il carattere faccina sorridente ( U+1F600) non può essere memorizzato in una singola unità di codice a 16 bit, quindi è suddiviso su 2. NSRangeconteggi basati su unità di codice a 16 bit. In questo esempio, 3 unità di codice rappresentano solo 2 caratteri
Codice diverso

25

Xcode 8 beta 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

o semplicemente

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


2

Usa in questo modo

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)

Uscita: Ciao


Vedo che hanno il tipo di dati "Range". Allora come posso usarlo?
vichhai

@vichhai Puoi elaborare di più dove vuoi usare?
Dharmbir Singh

Come nell'obiettivo c: intervallo NSRange;
vichhai

1

Trovo sorprendente che, anche in Swift 4, non ci sia ancora un modo nativo semplice per esprimere un intervallo di stringhe usando Int. Gli unici metodi String che consentono di fornire un Int come modo per ottenere una sottostringa per intervallo sono prefixe suffix.

È utile avere a portata di mano alcune utilità di conversione, in modo da poter parlare come NSRange quando si parla a una stringa. Ecco un'utilità che prende una posizione e una lunghezza, proprio come NSRange, e restituisce 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
}

Ad esempio, "hello".range(0,1)"è l' Range<String.Index>abbraccio del primo carattere di "hello". Come bonus, ho consentito posizioni negative: "hello".range(-1,1)"è l' Range<String.Index>abbraccio dell'ultimo personaggio di "hello".

È utile anche convertire a Range<String.Index>in NSRange, per quei momenti in cui devi parlare con Cocoa (ad esempio, quando hai a che fare con gli intervalli di attributi NSAttributedString). Swift 4 fornisce un modo nativo per farlo:

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

Possiamo quindi scrivere un'altra utility in cui andiamo direttamente da una posizione e una lunghezza di String a un NSRange:

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

1

Se qualcuno vuole creare un oggetto NSRange può creare come:

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

questo creerà un intervallo con posizione 0 e lunghezza 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

Ho creato la seguente estensione:

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
        }
    }
}

esempio di utilizzo:

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

Puoi usare in questo modo

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

come in

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

Voglio farlo:

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

Ma sfortunatamente, non posso scrivere un mio pedice perché quello detestato occupa lo spazio dei nomi.

Possiamo farlo comunque:

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

Aggiungilo al tuo progetto:

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])
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.