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)
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:
-
La i-ésima y la f-ésima comienzan con 0.
-
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.
-
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)
}
}