¿Cómo funciona la subcadena String en Swift?
He estado actualizando algunos de mis códigos y respuestas antiguos con Swift 3, pero cuando llegué a Swift Strings e Indexación con subcadenas, las cosas se volvieron confusas.
Específicamente estaba intentando lo siguiente:
let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)
donde la segunda línea me daba el siguiente error
El valor de tipo 'String' no tiene ningún miembro 'substringWithRange'
Veo que String
ahora tiene los siguientes métodos:
str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)
Al principio esto me confundió mucho, así que comencé a jugar con el índice y el rango . Esta es una pregunta y respuesta de seguimiento para la subcadena. Estoy agregando una respuesta a continuación para mostrar cómo se usan.
Todos los siguientes ejemplos utilizan
var str = "Hello, playground"
veloz 4
Las cadenas recibieron una revisión bastante grande en Swift 4. Cuando ahora obtienes alguna subcadena de una cadena, obtienes un Substring
tipo en lugar de un archivo String
. ¿Por qué es esto? Las cadenas son tipos de valores en Swift. Eso significa que si usa una cadena para crear una nueva, debe copiarla. Esto es bueno para la estabilidad (nadie más va a cambiarlo sin tu conocimiento) pero malo para la eficiencia.
Una subcadena, por otro lado, es una referencia a la cadena original de la que proviene. Aquí hay una imagen de la documentación que lo ilustra.
No es necesario realizar copias, por lo que su uso es mucho más eficiente. Sin embargo, imagina que obtienes una subcadena de diez caracteres de una cadena de un millón de caracteres. Debido a que la Subcadena hace referencia a la Cadena, el sistema tendría que retener la Cadena completa mientras la Subcadena esté disponible. Por lo tanto, cuando haya terminado de manipular su Subcadena, conviértala en una Cadena.
let myString = String(mySubstring)
Esto copiará solo la subcadena y se podrá recuperar la memoria que contiene la cadena antigua . Las subcadenas (como tipo) están destinadas a ser de corta duración.
Otra gran mejora en Swift 4 es que las cadenas son colecciones (nuevamente). Eso significa que todo lo que pueda hacer con una Colección, puede hacerlo con una Cadena (usar subíndices, iterar sobre los caracteres, filtrar, etc.).
Los siguientes ejemplos muestran cómo obtener una subcadena en Swift.
Obteniendo subcadenas
Puede obtener una subcadena a partir de una cadena mediante el uso de subíndices o de otros métodos (por ejemplo, prefix
, suffix
, split
). Sin embargo , aún necesita usar String.Index
y no un Int
índice para el rango. (Vea mi otra respuesta si necesita ayuda con eso).
Principio de una cuerda
Puede utilizar un subíndice (tenga en cuenta el rango unilateral de Swift 4):
let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello
o prefix
:
let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello
o incluso más fácil:
let mySubstring = str.prefix(5) // Hello
Fin de una cuerda
Usando subíndices:
let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground
o suffix
:
let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground
o incluso más fácil:
let mySubstring = str.suffix(10) // playground
Tenga en cuenta que cuando usé suffix(from: index)
tuve que contar hacia atrás desde el final usando -10
. Eso no es necesario cuando solo se usa suffix(x)
, que solo toma los últimos x
caracteres de una cadena.
Rango en una cadena
Nuevamente aquí simplemente usamos subíndices.
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
let mySubstring = str[range] // play
Convirtiendo Substring
aString
No olvide que cuando esté listo para guardar su subcadena, debe convertirla en a String
para que se pueda limpiar la memoria de la cadena anterior.
let myString = String(mySubstring)
¿ Usando una Int
extensión de índice?
Dudo en usar una Int
extensión de índice basada después de leer el artículo Strings in Swift 3 de Airspeed Velocity y Ole Begemann. Aunque en Swift 4 las cadenas son colecciones, el equipo de Swift no ha utilizado Int
índices a propósito. Aún es String.Index
. Esto tiene que ver con que los caracteres Swift estén compuestos por un número variable de puntos de código Unicode. El índice real debe calcularse de forma única para cada cadena.
Debo decir que espero que el equipo de Swift encuentre una manera de abstraerse String.Index
en el futuro. Pero hasta entonces, elijo utilizar su API. Me ayuda recordar que las manipulaciones de cadenas no son simples Int
búsquedas de índices.
Estoy realmente frustrado con el modelo de acceso a cadenas de Swift: todo tiene que ser un archivo Index
. Todo lo que quiero es acceder al carácter i-ésimo de la cadena usando Int
, no el índice torpe y avanzar (que cambia con cada versión importante). Entonces hice una extensión a String
:
extension String {
func index(from: Int) -> Index {
return self.index(startIndex, offsetBy: from)
}
func substring(from: Int) -> String {
let fromIndex = index(from: from)
return String(self[fromIndex...])
}
func substring(to: Int) -> String {
let toIndex = index(from: to)
return String(self[..<toIndex])
}
func substring(with r: Range<Int>) -> String {
let startIndex = index(from: r.lowerBound)
let endIndex = index(from: r.upperBound)
return String(self[startIndex..<endIndex])
}
}
let str = "Hello, playground"
print(str.substring(from: 7)) // playground
print(str.substring(to: 5)) // Hello
print(str.substring(with: 7..<11)) // play
Extensión Swift 5:
extension String {
subscript(_ range: CountableRange<Int>) -> String {
let start = index(startIndex, offsetBy: max(0, range.lowerBound))
let end = index(start, offsetBy: min(self.count - range.lowerBound,
range.upperBound - range.lowerBound))
return String(self[start..<end])
}
subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
let start = index(startIndex, offsetBy: max(0, range.lowerBound))
return String(self[start...])
}
}
Uso:
let s = "hello"
s[0..<3] // "hel"
s[3...] // "lo"
O unicode:
let s = "😎🤣😋"
s[0..<1] // "😎"