swift range

¿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)

}