¿Cómo crear rango en Swift?
range (9)
(1 .. <10)
devoluciones...
Rango = 1 .. <10
En Objective-c creamos rango usando NSRange
NSRange range;
Entonces, ¿cómo crear un rango en Swift?
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
Me parece sorprendente 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 suministrar un Int como una forma de obtener una subcadena por rango son
prefix
y
suffix
.
Es útil tener a la mano algunas utilidades de conversión, para que podamos hablar como NSRange al hablar con una cadena.
Aquí hay una utilidad que toma una ubicación y longitud, al igual que NSRange, y devuelve 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
}
Por ejemplo,
"hello".range(0,1)"
es el
Range<String.Index>
abarca el primer carácter de
"hello"
. Como
"hello".range(-1,1)"
adicional, he permitido ubicaciones negativas:
"hello".range(-1,1)"
es el
Range<String.Index>
abarca el último carácter de
"hello"
.
También es útil para convertir un
Range<String.Index>
en un NSRange, para esos momentos en los que tiene que hablar con Cocoa (por ejemplo, al tratar con los rangos de atributos NSAttributedString).
Swift 4 proporciona una forma nativa de hacer eso:
let nsrange = NSRange(range, in:s) // where s is the string
Por lo tanto, podemos escribir otra utilidad donde vamos directamente desde una ubicación de cadena y longitud a un NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
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
Si alguien quiere crear un objeto NSRange puede crearlo como:
let range: NSRange = NSRange.init(location: 0, length: 5)
esto creará un rango con posición 0 y longitud 5
Usar 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
Actualizado para Swift 4
Los rangos Swift son más complejos que
NSRange
, y no fueron más fáciles en Swift 3. Si quieres intentar entender el razonamiento detrás de algo de esta complejidad, lee
this
y
this
.
Solo te mostraré cómo crearlos y cuándo podrías usarlos.
Rangos cerrados:
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
ClosedRange
.
1.
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 desde 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, un
ClosedRange
no es contable (es decir, no se ajusta al protocolo de secuencia).
Eso significa que no puede iterar sobre los elementos con un bucle
for
.
Para eso necesitas el
CountableClosedRange
.
2.
CountableClosedRange
Esto es similar al último, 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])
}
Rangos medio abiertos:
a..<b
Este operador de rango incluye el elemento
a
pero
no el
elemento
b
.
Al igual que arriba, hay dos tipos diferentes de rangos medio abiertos:
Range
y
Range
CountableRange
.
1.
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"]
Sin embargo, una vez más, no puede iterar sobre un
Range
porque solo es comparable, no es estricto.
2.
CountableRange
Un
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])
}
NSRange
Todavía puede (debe) usar
NSRange
a veces en Swift (al hacer
cadenas atribuidas
, 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 inicial y el índice final.
El ejemplo aquí es similar en significado al rango Swift
3..<5
.
Sin embargo, dado que los tipos son diferentes, no son intercambiables.
Gamas con cuerdas
Los operadores de rango
...
y
..<
son una forma abreviada de crear rangos.
Por ejemplo:
let myRange = 1..<3
El camino largo para 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 para
String
porque las cadenas están hechas de caracteres y no todos los caracteres son del mismo tamaño.
(Lea
this
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 un
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 feliz toma 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 usted usa
Range<String.Index>
, no
Range<Int>
.
El índice de cadena se calcula en función de una cadena particular para que sepa si hay algún 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"
Rangos unilaterales:
a...
y
...b
..<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 usar rangos enteros unilaterales para iterar sobre colecciones. Aquí hay 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 los rangos de cadenas.
Si está haciendo un rango con
str.startIndex
o
str.endIndex
en un extremo, puede dejarlo desactivado.
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
Ver también:
- ¿Cómo funciona String.Index en Swift?
- ¿Cómo funciona la subcadena de cadena en Swift?
Notas
- No puede usar un rango que creó con una cadena en una cadena diferente.
- Como puede ver, los rangos de cadenas son un dolor en Swift, pero hacen que sea posible tratar mejor con emoji y otros escalares Unicode.
Estudio adicional
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
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)
}