index extension swift string range substring

extension - substring to string swift 3



ยฟCรณmo funciona la subcadena de cadena en Swift? (16)

He estado actualizando algunos de mis códigos y respuestas anteriores con Swift 3, pero cuando llegué a Swift Strings e Indexing 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 del tipo ''String'' no tiene 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<String.Index>)

Al principio, esto realmente me confundió, así que comencé a jugar con el índice y el rango . Esta es una pregunta y respuesta de seguimiento para la subcadena. Añado una respuesta a continuación para mostrar cómo se usan.


Swift 4

En rápida 4 String ajusta a la Collection . En lugar de substring , ahora deberíamos usar un subscript. Entonces, si desea eliminar solo la palabra "play" de "Hello, playground" , puede 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..<end] // The result is of type Substring

Es interesante saber que hacerlo le dará una Substring lugar de una String . Esto es rápido y eficiente ya que Substring comparte su almacenamiento con la cadena original. Sin embargo, compartir 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 la [documentación de Apple]. 1

Entonces, si por ejemplo obtiene un Range como resultado de una NSRegularExpression , podría 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..<end] return String(subString) } }


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

// // 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<String>? = ["Why", "substrings", "Swift3"] FindSubString(inputStr: Bigstring, subStrings: searchStrs) func FindSubString(inputStr : String, subStrings: Array<String>?) -> 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<String.Index> = 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 }

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 cadenas en swift (cubre swift 4 también)

O puedes ver esta esencia en github


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

let mid = t.index(t.endIndex, offsetBy:-5) let firstHalf = t[..<mid] let secondHalf = t[mid...]


Estoy realmente frustrado con el modelo de acceso a cadenas de Swift: 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 avanzar (que cambia con cada lanzamiento 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 substring(from: fromIndex) } func substring(to: Int) -> String { let toIndex = index(from: to) return substring(to: toIndex) } func substring(with r: Range<Int>) -> String { let startIndex = index(from: r.lowerBound) let endIndex = index(from: r.upperBound) return substring(with: 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 4:

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

Uso:

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

O unicode:

let s = "๐Ÿ˜Ž๐Ÿคฃ๐Ÿ˜‹" s[0..<1] // "๐Ÿ˜Ž"


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

Compilé este ejemplo de cómo obtener posiciones para las subcadenas de texto más grande:

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

devuelve ("Why", 0, 3) ("subcadenas", 26, 36) ("Swift3", 40, 46)


Sobre la base de lo anterior, necesitaba dividir una cadena en un carácter que no se imprime y se cae el carácter que no se imprime. Desarrollé dos métodos:

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

que reuní usando algunas de las respuestas anteriores.

Como una cadena es una colección, hice lo siguiente:

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)..<result.index(result.endIndex, offsetBy: -1)]) } return result } else if let _from: Character = from { guard let startOfSentence: String.Index = self.index(of: _from) else { return nil } let result: String = String(self[startOfSentence...]) if include == false { guard result.count > 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[..<result.index(result.endIndex, offsetBy: -1)]) } return result } return nil } }

Lo que para mí fue más intuitivo. ¿Cuál es el mejor? No tengo forma de decir que ambos trabajan con Swift 5


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

let f = 6 print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )]) //print cdefg

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

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) ) //print cdefg

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

extension String { subscript(_ i: Int) -> String { let idx1 = index(startIndex, offsetBy: i) let idx2 = index(idx1, offsetBy: 1) return String(self[idx1..<idx2]) } subscript (r: Range<Int>) -> String { let start = index(startIndex, offsetBy: r.lowerBound) let end = index(startIndex, offsetBy: r.upperBound) return String(self[start ..< end]) } subscript (r: CountableClosedRange<Int>) -> String { let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound) let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound) return String(self[startIndex...endIndex]) } }

Notas:

  1. La i-ésima y la f-ésima comienzan con 0.

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

  3. Por supuesto, debe incluir errores de validación como índice no válido.


Soy un pensamiento bastante mecánico. Aquí están los conceptos básicos ...

Swift 4 Swift 5

var fString = String() for (n,c) in str.enumerated(){ *if c == "/u{1A}" { print(fString); let lString = str.dropFirst(n + 1) print(lString) break } fString += String(c) }*

El resultado es una subcadena, lo que significa que es parte de la cadena original. Para obtener una cadena separada completa, solo use eg

let t = "abracadabra" let start1 = t.index(t.startIndex, offsetBy:0) let end1 = t.index(t.endIndex, offsetBy:-5) let start2 = t.index(t.endIndex, offsetBy:-5) let end2 = t.index(t.endIndex, offsetBy:0) let t2 = t[start1 ..< end1] let t3 = t[start2 ..< end2] //or a shorter form let t4 = t[..<end1] let t5 = t[start2...] print("/(t2) /(t3) /(t)") print("/(t4) /(t5) /(t)") // result: // abraca dabra abracadabra

Esto es lo que uso:

String(t3) String(t4)


Swift 4

"Subcadena" ( 1 ):

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"

Ejemplo de cadena de extensión:

extension String { func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound { return String(self[range]) } func index(at: Int) -> Index { return self.index(self.startIndex, offsetBy: at) } }

ejemplo de uso de la cadena de extensión:

let item = "Fred looks funny" item.subString(item.index(at: 2)...) // "ed looks funny"


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

Sin embargo, me di cuenta por experiencia de que siempre sufro las consecuencias de tratar de luchar contra el "cambio", como tratar con personajes de varios bytes, lo cual es inevitable si estás mirando 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 es solo una solución alternativa para facilitarle la vida (no digo que sean incorrectas o costosas), ¿por qué no descubrir cómo las Cuerdas ahora están diseñadas para funcionar?

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

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

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

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..<endIndex]) }else{ return nil } }

Espero que esto contribuya ...


Todos los siguientes ejemplos usan

var str = "Hello, playground"

Swift 4

Las cadenas obtuvieron 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 valor 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 lo va a cambiar sin su 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 documentation que ilustra eso.

No se necesita copiar, por lo que es mucho más eficiente de usar. Sin embargo, imagina que tienes 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 toda la Cadena mientras la Subcadena esté alrededor. 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 antigua cadena se puede recolectar basura. 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 cualquier cosa que pueda hacer a una Colección, puede hacerlo a 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 de una cadena mediante el uso de subíndices o varios otros métodos (por ejemplo, prefix , suffix , split ). Sin embargo, aún necesita usar String.Index y no un índice Int para el rango. (Vea mi otra respuesta si necesita ayuda con eso).

Comienzo de una cuerda

Puede usar un subíndice (tenga en cuenta el rango unilateral 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 al usar el suffix(from: index) tuve que contar desde el final usando -10 . Eso no es necesario cuando se usa el suffix(x) , que solo toma los últimos x caracteres de una cadena.

Rango en una cuerda

Nuevamente, simplemente usamos subíndices aquí.

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

Convertir Substring a String

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

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 Strings in Swift 3 de Airspeed Velocity y Ole Begemann. Aunque en Swift 4, las cadenas son colecciones, el equipo de Swift deliberadamente no ha usado índices Int . Sigue siendo 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 tiene que calcularse de manera única para cada cadena.

Tengo que decir que espero que el equipo de Swift encuentre una forma de abstraer String.Index en el futuro. Pero hasta ellos elijo usar su API. Me ayuda a recordar que las manipulaciones de cadenas no son simples búsquedas de índice Int .


Aquí hay una implementación más genérica:

Esta técnica todavía utiliza el index para mantener los estándares de Swift e implica un personaje completo.

extension String { func take(_ n: Int) -> String { guard n >= 0 else { fatalError("n should never negative") } let index = self.index(self.startIndex, offsetBy: min(n, self.count)) return String(self[..<index]) } }

Para subcadenas del tercer carácter:

let text = "Hello, World!" let substring = text.take(5) //Hello

He usado camel subString para indicar que devuelve una String y no una Substring .


Swift 4

var str = "abc/u{1A}12345sdf" let range1: Range<String.Index> = str.range(of: "/u{1A}")! let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound) let start = str.index(str.startIndex, offsetBy: index1) let end = str.index(str.endIndex, offsetBy: -0) let result = str[start..<end] // The result is of type Substring let firstStr = str[str.startIndex..<range1.lowerBound]


Swift 4 y 5:

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

Cómo usarlo:

"abcde" [0] -> "a"

"abcde" [0 ... 2] -> "abc"

"abcde" [2 .. <4] -> "cd"


Swift 4+

extension String { subscript(_ i: Int) -> String { let idx1 = index(startIndex, offsetBy: i) let idx2 = index(idx1, offsetBy: 1) return String(self[idx1..<idx2]) } } let s = "hello" s[0] // h s[1] // e s[2] // l s[3] // l s[4] // o

Devuelve una subsecuencia de los primeros n caracteres, o la cadena completa si la cadena es más corta. (inspirado en: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html )

Ejemplo:

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..<end) } }