¿Cómo crear rango en Swift?

103

En Objective-c creamos rango usando NSRange

NSRange range;

Entonces, ¿cómo crear rango en Swift?

vichhai
fuente
3
¿Para qué necesitas la gama? Se usan cadenas rápidas Range<String.Index>, pero a veces es necesario trabajar con NSStringy NSRange, por lo que sería útil algo más de contexto. - Pero eche un vistazo a stackoverflow.com/questions/24092884/… .
Martin R

Respuestas:

256

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.

Rangos cerrados: a...b

Este operador de rango crea un rango Swift que incluye tanto el elemento a como el elemento b, incluso si bes el valor máximo posible para un tipo (como Int.max). Hay dos tipos diferentes de rangos cerrados: ClosedRangey CountableClosedRange.

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 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 ClosedRangeno es contable (es decir, no se ajusta al protocolo de secuencia). Eso significa que no puede iterar sobre los elementos con un forbucle. Para eso necesitas el CountableClosedRange.

2. 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])
}

Rangos semiabiertos: a..<b

Este operador de rango incluye elemento apero no elemento b. Como arriba, hay dos tipos diferentes de rangos semiabiertos: Rangey 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"]

Una vez más, sin embargo, no se puede iterar sobre un Rangeporque solo es comparable, no stridable.

2. CountableRange

A CountableRangepermite 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

Puede (debe) usarlo NSRangea 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.

Rangos con cadenas

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 Stringporque 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 NSRangey NSStringcon 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"

Rangos unilaterales: a...y ...by..<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.startIndexo str.endIndexen 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

Ver también:

Notas

  • No puede usar un rango que creó con una cadena en una cadena diferente.
  • Como puede ver, los rangos de cadenas son una molestia en Swift, pero hacen que sea posible lidiar mejor con emoji y otros escalares Unicode.

Estudio adicional

Suragch
fuente
Mi comentario se refiere a la subsección "Problema con NSRange". NSStringalmacena 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. NSRangerecuentos basados ​​en unidades de código de 16 bits. En este ejemplo, 3 unidades de código representan solo 2 caracteres
Código diferente
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 simplemente

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

(1 .. <10)

devoluciones...

Rango = 1 .. <10

Victor Lin
fuente
¿Cómo responde esto a la pregunta?
Machavity
2

Ú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

Dharmbir Singh
fuente
Veo que tienen el tipo de datos "Rango". Entonces, ¿cómo puedo usarlo?
vichhai
@vichhai ¿Puedes explicar más dónde quieres usar?
Dharmbir Singh
Como en el objetivo c: NSRange range;
vichhai
1

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

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)
    }
}
mate
fuente
1

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

camioneta
fuente
0

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
dr OX
fuente
0

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
Mohammad Nurdin
fuente
0

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])
        }
    }
}
ScottyBlades
fuente
0
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)
}
Abo3atef
fuente