Respuestas:
Actualizado para Swift 4
Los rangos de Swift son más complejos que NSRange
, y no se volvieron más fáciles en Swift 3. Si quieres tratar de comprender el razonamiento detrás de esta complejidad, lee esto y esto . Solo te mostraré cómo crearlos y cuándo puedes usarlos.
a...b
Este operador de rango crea un rango Swift que incluye tanto el elemento a
como el elemento b
, incluso si b
es el valor máximo posible para un tipo (como Int.max
). Hay dos tipos diferentes de rangos cerrados: ClosedRange
y CountableClosedRange
.
ClosedRange
Los elementos de todos los rangos en Swift son comparables (es decir, se ajustan al protocolo Comparable). Eso le permite acceder a los elementos en el rango de una colección. Aquí hay un ejemplo:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Sin embargo, a ClosedRange
no es contable (es decir, no se ajusta al protocolo de secuencia). Eso significa que no puede iterar sobre los elementos con un for
bucle. Para eso necesitas el CountableClosedRange
.
CountableClosedRange
Esto es similar al anterior, excepto que ahora el rango también se puede iterar.
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
Este operador de rango incluye elemento a
pero no elemento b
. Como arriba, hay dos tipos diferentes de rangos semiabiertos: Range
y CountableRange
.
Range
Al igual que con ClosedRange
, puede acceder a los elementos de una colección con un Range
. Ejemplo:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Una vez más, sin embargo, no se puede iterar sobre un Range
porque solo es comparable, no stridable.
CountableRange
A CountableRange
permite la iteración.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Puede (debe) usarlo NSRange
a veces en Swift (al hacer cadenas con atributos , por ejemplo), por lo que es útil saber cómo hacer una.
let myNSRange = NSRange(location: 3, length: 2)
Tenga en cuenta que esta es la ubicación y la longitud , no el índice de inicio y el índice de finalización. El ejemplo aquí tiene un significado similar al de la gama Swift 3..<5
. Sin embargo, dado que los tipos son diferentes, no son intercambiables.
Los operadores de rango ...
y ..<
son una forma abreviada de crear rangos. Por ejemplo:
let myRange = 1..<3
La forma más larga de crear el mismo rango sería
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Puede ver que el tipo de índice aquí es Int
. Sin embargo, eso no funciona String
porque las cadenas están hechas de caracteres y no todos los caracteres tienen el mismo tamaño. (Lea esto para obtener más información). Un emoji como 😀, por ejemplo, ocupa más espacio que la letra "b".
Problema con NSRange
Intenta experimentar con NSRange
y NSString
con emoji y verás a qué me refiero. Dolor de cabeza.
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 carita sonriente necesita dos unidades de código UTF-16 para almacenar, por lo que da el resultado inesperado de no incluir la "d".
Solución rápida
Debido a esto, con Swift Strings usas Range<String.Index>
, no Range<Int>
. El índice de cadenas se calcula en función de una cadena en particular para que sepa si hay emoji o grupos de grafemas extendidos.
Ejemplo
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...
y ...b
y..<b
En Swift 4 las cosas se simplificaron un poco. Siempre que se pueda inferir el punto inicial o final de un rango, puede dejarlo.
En t
Puede utilizar rangos de enteros de un solo lado para iterar sobre colecciones. A continuación se muestran algunos ejemplos de la documentación .
// 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...
Cuerda
Esto también funciona con rangos de cadenas. Si está haciendo un rango con str.startIndex
o str.endIndex
en un extremo, puede dejarlo. El compilador lo inferirá.
Dado
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
Puede ir del índice a str.endIndex usando ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
Notas
NSString
almacena internamente sus caracteres en codificación UTF-16. Un escalar Unicode completo es de 21 bits. El carácter de cara sonriente ( U+1F600
) no se puede almacenar en una sola unidad de código de 16 bits, por lo que se distribuye en 2. NSRange
recuentos basados en unidades de código de 16 bits. En este ejemplo, 3 unidades de código representan solo 2 caracteres
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 simplemente
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
Úselo así
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)
Salida: Hola
Me sorprende que, incluso en Swift 4, todavía no haya una forma nativa simple de expresar un rango de cadenas usando Int. Los únicos métodos de cadena que le permiten proporcionar un Int como una forma de obtener una subcadena por rango son prefix
ysuffix
.
Es útil tener a mano algunas utilidades de conversión, para que podamos hablar como NSRange cuando hablamos con un String. Aquí hay una utilidad que toma una ubicación y una longitud, como NSRange, y devuelve 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
}
Por ejemplo, "hello".range(0,1)"
es el Range<String.Index>
abrazo el primer personaje de "hello"
. Como beneficio adicional, he permitido ubicaciones negativas: "hello".range(-1,1)"
es el Range<String.Index>
abrazo el último carácter de"hello"
.
También es útil convertir Range<String.Index>
a en NSRange, para esos momentos en los que tienes que hablar con Cocoa (por ejemplo, al tratar con rangos de atributos NSAttributedString). Swift 4 proporciona una forma nativa de hacerlo:
let nsrange = NSRange(range, in:s) // where s is the string
Por lo tanto, podemos escribir otra utilidad en la que vayamos directamente desde una ubicación y longitud de String a un NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
Creé la siguiente extensión:
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
}
}
}
ejemplo de uso:
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
Puedes usar así
let nsRange = NSRange(location: someInt, length: someInt)
como en
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
Quiero hacer esto:
print("Hello"[1...3])
// out: Error
Pero, desafortunadamente, no puedo escribir un subíndice propio porque el odiado ocupa el espacio del nombre.
Sin embargo, podemos hacer esto:
print("Hello"[range: 1...3])
// out: ell
Simplemente agregue esto a su proyecto:
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>
, pero a veces es necesario trabajar conNSString
yNSRange
, por lo que sería útil algo más de contexto. - Pero eche un vistazo a stackoverflow.com/questions/24092884/… .