¿Cómo funciona la subcadena String en Swift?

He estado actualizando algunos de mis códigos y respuestas anteriores 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 estaba dando el siguiente error

El valor del tipo ‘String’ no tiene ningún miembro ‘substringWithRange’

Veo que String tiene los siguientes métodos ahora:

 str.substring(to: String.Index) str.substring(from: String.Index) str.substring(with: Range) 

Estos me confundían al principio, así que comencé a jugar con el índice y el rango . Esta es una pregunta y respuesta de seguimiento para subserie. Estoy agregando una respuesta a continuación para mostrar cómo se usan.

enter image description here

Todos los siguientes ejemplos usan

 var str = "Hello, playground" 

Swift 4

Strings obtuvo una revisión bastante grande en Swift 4. Cuando obtienes una subcadena de una Cadena ahora, obtienes un tipo de Substring en lugar de una String . ¿Por qué es esto? Las cadenas son tipos de valores en Swift. Eso significa que si usa una cadena para hacer una nueva, entonces debe copiarse. Esto es bueno para la estabilidad (nadie más va a cambiarlo sin su conocimiento) pero es malo para la eficiencia.

Una Subcadena, por otro lado, es una referencia de regreso a la Cadena original de la que proviene. Aquí hay una imagen de la documentación que ilustra eso.

No se necesita copiar, por lo que es mucho más eficiente de usar. Sin embargo, imagine que tiene una Subcadena de diez caracteres de un Cadena de un millón de caracteres. Debido a que la Subcadena está haciendo referencia a la Cadena, el sistema tendría que aferrarse a toda la Cadena durante todo el tiempo que la Subcadena esté cerca. Por lo tanto, cada vez que termine de manipular su subcadena, conviértala en una cadena.

 let myString = String(mySubstring) 

Esto copiará solo la subcadena y la cadena anterior se puede recolectar como basura. Las subcadenas (como un 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 cualquier cosa que pueda hacer para una Colección, puede hacer una Cadena (usar subíndices, iterar sobre los caracteres, filtrar, etc.).

Los siguientes ejemplos muestran cómo obtener una subcadena en Swift.

Obtener subcadenas

Puede obtener una subcadena de una cadena mediante el uso de subíndices o una serie de otros métodos (por ejemplo, prefix , suffix , split ). Sin embargo, todavía necesita utilizar String.Index y no un índice Int para el rango. (Vea mi otra respuesta si necesita ayuda con eso).

Principio de una cuerda

Puede usar un subíndice (tenga en cuenta el rango Swift 4 de una cara):

 let index = str.index(str.startIndex, offsetBy: 5) let mySubstring = str[.. 

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 cadena

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 al usar el suffix(from: index) tuve que contar desde el final usando -10 . Eso no es necesario cuando solo usas el suffix(x) , que solo toma los últimos x caracteres de una Cadena.

Rango en una cadena

De nuevo, simplemente usamos subíndices aquí.

 let start = str.index(str.startIndex, offsetBy: 7) let end = str.index(str.endIndex, offsetBy: -6) let range = start.. 

Conversión de Substring a String

No olvide que, cuando esté listo para guardar su subcadena, debe convertirla en una String para que la memoria de la cadena anterior pueda limpiarse.

 let myString = String(mySubstring) 

¿Usando una extensión de índice Int ?

Dudo en usar una extensión de índice basada en Int después de leer el artículo Cuerdas en Swift 3 de Airspeed Velocity y Ole Begemnn. Aunque en Swift 4, las cadenas son colecciones, el equipo de Swift deliberadamente no ha usado índices Int . Todavía es String.Index . Esto tiene que ver con que los personajes Swift estén compuestos por números variables de puntos de código Unicode. El índice real debe calcularse de manera única para cada cadena.

Debo decir que espero que el equipo de Swift encuentre una forma de abstraer String.Index en el futuro. Pero hasta que decidan usar su API. Me ayuda a recordar que las manipulaciones de cadenas no son simples búsquedas de índices de Internet.

Estoy realmente frustrado con el modelo de Swift’s String access: todo tiene que ser un Index . Todo lo que quiero es acceder al carácter i-ésimo de la cadena usando Int , no el índice torpe y avanzando (lo que sucede a cambiar con cada versión principal). Así que 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 substring(from: fromIndex) } func substring(to: Int) -> String { let toIndex = index(from: to) return substring(to: toIndex) } func substring(with r: Range) -> String { let startIndex = index(from: r.lowerBound) let endIndex = index(from: r.upperBound) return substring(with: startIndex.. 

Swift 4 Extension:

 extension String { subscript(_ range: CountableRange) -> String { let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound)) let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound)) return String(self[idx1.. 

Uso:

 let s = "hello" s[0..<3] // "hel" s[3.. 

O unicode:

 let s = "😎🤣😋" s[0..<1] // "😎" 

Swift 4

En swift 4 String ajusta a Collection . En lugar de substring , ahora deberíamos usar un subscript. Entonces, si quieres cortar solo la palabra "play" de "Hello, playground" , podrías hacerlo así:

 var str = "Hello, playground" let start = str.index(str.startIndex, offsetBy: 7) let end = str.index(str.endIndex, offsetBy: -6) let result = str[start.. 

Es interesante saber que al hacerlo, obtendrás una Substring lugar de una String . Esto es rápido y eficiente ya que Substring comparte su almacenamiento con el String original. Sin embargo, compartir la memoria de esta manera también puede conducir fácilmente a pérdidas de memoria.

Es por eso que debe copiar el resultado en una nueva Cadena, una vez que desee limpiar la Cadena original. Puedes hacer esto usando el constructor normal:

 let newString = String(result) 

Puede encontrar más información sobre la nueva clase Substring en [Documentación de Apple]. 1

Entonces, si, por ejemplo, obtiene un Range como resultado de una NSRegularExpression , puede usar la siguiente extensión:

 extension String { subscript(_ range: NSRange) -> String { let start = self.index(self.startIndex, offsetBy: range.lowerBound) let end = self.index(self.startIndex, offsetBy: range.upperBound) let subString = self[start.. 

Tuve la misma reacción inicial. Yo también estaba frustrado por cómo la syntax y los objetos cambian tan drásticamente en cada lanzamiento principal.

Sin embargo, me di cuenta por experiencia de cómo siempre eventualmente sufro las consecuencias de tratar de luchar contra el “cambio”, como tratar con personajes de múltiples bytes, lo cual es inevitable si miras a una audiencia global.

Así que decidí reconocer y respetar los esfuerzos realizados por los ingenieros de Apple y hacer mi parte al comprender su mentalidad cuando se les ocurrió este enfoque “horrible”.

En lugar de crear extensiones que son solo una solución para facilitarle la vida (no estoy diciendo que sean incorrectas o costosas), ¿por qué no descubrir cómo las cadenas ahora están diseñadas para funcionar?

Por ejemplo, tenía este código que estaba trabajando en Swift 2.2:

 let rString = cString.substringToIndex(2) let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2) let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2) 

y luego de dejar de intentar el mismo enfoque trabajando, por ejemplo, usando Substrings, finalmente entendí el concepto de tratar a Strings como una colección bidireccional para la cual terminé con esta versión del mismo código:

 let rString = String(cString.characters.prefix(2)) cString = String(cString.characters.dropFirst(2)) let gString = String(cString.characters.prefix(2)) cString = String(cString.characters.dropFirst(2)) let bString = String(cString.characters.prefix(2)) 

Espero que esto contribuya …

Soy nuevo en Swift 3, pero buscando la syntax String (index) para la analogía, creo que el índice es como un “puntero” restringido a la cadena e Int puede ayudar como un objeto independiente. Usando la syntax base + offset, podemos obtener el carácter i-ésimo de la cadena con el siguiente código:

 let s = "abcdefghi" let i = 2 print (s[s.index(s.startIndex, offsetBy:i)]) // print c 

Para un rango de caracteres (índices) a partir de una cadena que utiliza la syntax String (rango), podemos obtener desde i-th hasta f-th caracteres con el siguiente código:

 let f = 6 print (s[s.index(s.startIndex, offsetBy:i ).. 

Para una subcadena (rango) desde una cadena usando String.substring (rango) podemos obtener la subcadena usando el siguiente código:

 print (s.substring (with:s.index(s.startIndex, offsetBy:i ).. 

Notas:

  1. El i-th y f-th comienzan con 0.

  2. Para f-th, utilizo offsetBY: f + 1, porque el rango de uso de suscripción ... < (operador medio abierto), no incluye la posición f-ésima.

  3. Por supuesto, debe incluir validar errores como índice no válido.

Aquí hay una función que devuelve una subcadena de una subcadena dada cuando se proporcionan los índices de inicio y final. Para una referencia completa, puede visitar los enlaces que figuran a continuación.

 func substring(string: String, fromIndex: Int, toIndex: Int) -> String? { if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{ let startIndex = string.index(string.startIndex, offsetBy: fromIndex) let endIndex = string.index(string.startIndex, offsetBy: toIndex) return String(string[startIndex.. 

Aquí hay un enlace a la publicación del blog que he creado para tratar la manipulación de cadenas de forma rápida. Manipulación de cuerdas en veloz (también cubre 4 swift)

O puedes ver esta esencia en github

La misma frustración, esto no debería ser tan difícil …

Recopilé este ejemplo de obtención de posiciones para subcadenas de textos más grandes:

 // // Play with finding substrings returning an array of the non-unique words and positions in text // // import UIKit let Bigstring = "Why is it so hard to find substrings in Swift3" let searchStrs : Array? = ["Why", "substrings", "Swift3"] FindSubString(inputStr: Bigstring, subStrings: searchStrs) func FindSubString(inputStr : String, subStrings: Array?) -> Array< (String, Int, Int)> { var resultArray : Array< (String, Int, Int)> = [] for i: Int in 0...(subStrings?.count)!-1 { if inputStr.contains((subStrings?[i])!) { let range: Range = inputStr.range(of: subStrings![i])! let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound) let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound) let element = ((subStrings?[i])! as String, lPos, uPos) resultArray.append(element) } } for words in resultArray { print(words) } return resultArray } 

devoluciones (“Por qué”, 0, 3) (“subcadenas”, 26, 36) (“Swift3”, 40, 46)

Creé una extensión simple para esto (Swift 3)

 extension String { func substring(location: Int, length: Int) -> String? { guard characters.count >= location + length else { return nil } let start = index(startIndex, offsetBy: location) let end = index(startIndex, offsetBy: location + length) return substring(with: start.. 

Swift 4

 extension String { subscript(_ i: Int) -> String { let idx1 = index(startIndex, offsetBy: i) let idx2 = index(idx1, offsetBy: 1) return String(self[idx1.. 

Swift 4

“Subcadena” ( https://developer.apple.com/documentation/swift/substring ):

 let greeting = "Hi there! It's nice to meet you! 👋" let endOfSentence = greeting.index(of: "!")! let firstSentence = greeting[...endOfSentence] // firstSentence == "Hi there!" 

Ejemplo de cadena de extensión:

 private typealias HowDoYouLikeThatElonMusk = String private extension HowDoYouLikeThatElonMusk { subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? { if let _from: Character = from, let _to: Character = to { let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self) guard let startOfSentence: String.Index = self.index(of: _from), let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else { return nil } let result: String = String(self[startOfSentence...endOfSentence]) if include == false { guard result.count > 2 else { return nil } return String(result[result.index(result.startIndex, offsetBy: 1).. 1 else { return nil } return String(result[result.index(result.startIndex, offsetBy: 1)...]) } return result } else if let _to: Character = to { guard let endOfSentence: String.Index = self.index(of: _to) else { return nil } let result: String = String(self[...endOfSentence]) if include == false { guard result.count > 1 else { return nil } return String(result[.. 

ejemplo de usar la extensión Cadena:

 let source = ">>>01234..56789< <<" // include = true var from = source["3", nil, true] // "34..56789<<<" var to = source[nil, "6", true] // ">>>01234..56" var fromTo = source["3", "6", true] // "34..56" let notFound = source["a", nil, true] // nil // include = false from = source["3", nil, false] // "4..56789< <<" to = source[nil, "6", false] // ">>>01234..5" fromTo = source["3", "6", false] // "4..5" let outOfBounds = source[".", ".", false] // nil let str = "Hello, playground" let hello = str[nil, ",", false] // "Hello"