substrings - Índice de una subcadena en una cadena con Swift
swift 4 string (8)
En Swift 4:
Obtener el índice de un personaje en una cadena:
let str = "abcdefghabcd"
if let index = str.index(of: "b") {
print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}
Crear SubString (prefijo y sufijo) a partir de String usando Swift 4:
let str : String = "ilike"
for i in 0...str.count {
let index = str.index(str.startIndex, offsetBy: i) // String.Index
let prefix = str[..<index] // String.SubSequence
let suffix = str[index...] // String.SubSequence
print("prefix /(prefix), suffix : /(suffix)")
}
Salida
prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix :
Si desea generar una subcadena entre 2 índices, use:
let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
Estoy acostumbrado a hacer esto en JavaScript:
var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"
Swift no tiene esta función, ¿cómo hacer algo similar?
¿Has considerado usar NSRange?
if let range = mainString.range(of: mySubString) {
//...
}
Aquí hay tres problemas estrechamente relacionados:
-
Todos los métodos de búsqueda de subcadenas han terminado en el mundo de Cocoa NSString (Fundación)
-
Foundation NSRange no coincide con Swift Range; el primero usa inicio y longitud, el segundo usa puntos finales
-
En general, los caracteres Swift se indexan usando
String.Index
, no Int, pero los caracteres Foundation se indexan usando Int, y no existe una traducción directa simple entre ellos (porque Foundation y Swift tienen ideas diferentes de lo que constituye un carácter)
Dado todo eso, pensemos en cómo escribir:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
// ?
}
La subcadena
s2
debe buscarse en
s
utilizando un método de String Foundation.
El rango resultante vuelve a nosotros,
no
como un NSRange (aunque este es un método de Foundation), sino como un Range of
String.Index
(envuelto en un Opcional, en caso de que no encontremos la subcadena).
Sin embargo, el otro número,
from
, es un Int.
Por lo tanto, no podemos formar ningún tipo de rango que los involucre a ambos.
¡Pero no tenemos que hacerlo!
Todo lo que tenemos que hacer es cortar el
final
de nuestra cadena original usando un método que tome un
String.Index
, y cortar el
comienzo
de nuestra cadena original usando un método que tome un Int.
Afortunadamente, tales métodos existen!
Me gusta esto:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
guard let r = s.range(of:s2) else {return nil}
var s = s.prefix(upTo:r.lowerBound)
s = s.dropFirst(from)
return s
}
O, si prefiere poder aplicar este método directamente a una cadena, como esta ...
let output = "abcde".substring(from:0, toSubstring:"cd")
... luego conviértalo en una extensión en String:
extension String {
func substring(from:Int, toSubstring s2 : String) -> Substring? {
guard let r = self.range(of:s2) else {return nil}
var s = self.prefix(upTo:r.lowerBound)
s = s.dropFirst(from)
return s
}
}
En la versión 3 de Swift, String no tiene funciones como:
str.index(of: String)
Si se requiere el índice para una subcadena, una de las formas es obtener el rango. Tenemos las siguientes funciones en la cadena que devuelve el rango:
str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)
Por ejemplo, para encontrar los índices de primera aparición de juego en str
var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4
Nota: el rango es opcional. Si no puede encontrar la cadena, la hará nula. Por ejemplo
var str = "play play play"
var range = str.range(of: "zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
Hacer esto en Swift es posible pero requiere más líneas, aquí hay una función
indexOf()
hace lo que se espera:
func indexOf(source: String, substring: String) -> Int? {
let maxIndex = source.characters.count - substring.characters.count
for index in 0...maxIndex {
let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count)
if source.substringWithRange(rangeSubstring) == substring {
return index
}
}
return nil
}
var str = "abcde"
if let indexOfCD = indexOf(str, substring: "cd") {
let distance = str.startIndex.advancedBy(indexOfCD)
print(str.substringToIndex(distance)) // Returns "ab"
}
Esta función no está optimizada pero hace el trabajo para cadenas cortas.
La respuesta de Leo Dabus es genial.
Aquí está mi respuesta basada en su respuesta usando
compactMap
para evitar
compactMap
de
Index out of range
.
Swift 5.1
extension StringProtocol { func ranges(of targetString: Self, options: String.CompareOptions = [], locale: Locale? = nil) -> [Range<String.Index>] { let result: [Range<String.Index>] = self.indices.compactMap { startIndex in let targetStringEndIndex = index(startIndex, offsetBy: targetString.count, limitedBy: endIndex) ?? endIndex return range(of: targetString, options: options, range: startIndex..<targetStringEndIndex, locale: locale) } return result } } // Usage let str = "Hello, playground, playground, playground" let ranges = str.ranges(of: "play") ranges.forEach { print("[/($0.lowerBound.utf16Offset(in: str)), /($0.upperBound.utf16Offset(in: str))]") } // result - [7, 11], [19, 23], [31, 35]
Usando el subíndice
String[Range<String.Index>]
puede obtener la
String[Range<String.Index>]
.
Necesita crear el índice inicial y el último índice para crear el rango y puede hacerlo de la siguiente manera
let str = "abcde"
if let range = str.range(of: "cd") {
let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound]
print(substring) // Prints ab
}
else {
print("String not present")
}
Si no define el índice de inicio de este operador
..<
, toma el índice de inicio.
También puede usar
str[str.startIndex..<range.lowerBound]
lugar de
str[..<range.lowerBound]
editar / actualizar:
Xcode 11 • Swift 5.1 o posterior
extension StringProtocol { // for Swift 4.x syntax you will needed also to constrain the collection Index to String Index - `extension StringProtocol where Index == String.Index`
func index(of string: Self, options: String.CompareOptions = []) -> Index? {
return range(of: string, options: options)?.lowerBound
}
func endIndex(of string: Self, options: String.CompareOptions = []) -> Index? {
return range(of: string, options: options)?.upperBound
}
func indexes(of string: Self, options: String.CompareOptions = []) -> [Index] {
var result: [Index] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let range = self[startIndex...].range(of: string, options: options) {
result.append(range.lowerBound)
startIndex = range.lowerBound < range.upperBound ? range.upperBound :
index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
func ranges(of string: Self, options: String.CompareOptions = []) -> [Range<Index>] {
var result: [Range<Index>] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let range = self[startIndex...].range(of: string, options: options) {
result.append(range)
startIndex = range.lowerBound < range.upperBound ? range.upperBound :
index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
}
uso:
let str = "abcde"
if let index = str.index(of: "cd") {
let substring = str[..<index] // ab
let string = String(substring)
print(string) // "ab/n"
}
let str = "Hello, playground, playground, playground"
str.index(of: "play") // 7
str.endIndex(of: "play") // 11
str.indexes(of: "play") // [7, 19, 31]
str.ranges(of: "play") // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]
muestra insensible a mayúsculas y minúsculas
let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] } //
print(matches) // ["play", "play", "play"]
muestra de expresión regular
let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "//b/(escapedQuery)//w+" // matches any word that starts with "play" prefix
let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }
print(matches) // ["playground", "playground", "playground"]