Risposte:
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.
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: ClosedRange
e CountableClosedRange
.
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 ClosedRange
non è numerabile (cioè, non è conforme al protocollo Sequence). Ciò significa che non puoi iterare sugli elementi con un for
ciclo. Per questo hai bisogno del file CountableClosedRange
.
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])
}
a..<b
Questo operatore di intervallo include l'elemento a
ma non l' elemento b
. Come sopra, ci sono due diversi tipi di intervalli semiaperti: Range
e CountableRange
.
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 Range
perché è solo comparabile, non stridabile.
CountableRange
A CountableRange
consente 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])
}
A volte puoi (devi) usare ancora NSRange
in 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.
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 NSRange
e NSString
con 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"
a...
e ...b
e..<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.startIndex
o str.endIndex
ad 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
Appunti
NSString
memorizza 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. NSRange
conteggi basati su unità di codice a 16 bit. In questo esempio, 3 unità di codice rappresentano solo 2 caratteri
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
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
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 prefix
e 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)
}
}
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
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
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])
}
}
}
Range<String.Index>
, ma a volte è necessario lavorare conNSString
eNSRange
, quindi un po 'più di contesto sarebbe utile. - Ma dai un'occhiata a stackoverflow.com/questions/24092884/… .