Coincidencias de expresiones regulares de extracción rápida

Resuelto mitchkman asked hace 54 años • 16 respuestas

Quiero extraer subcadenas de una cadena que coincida con un patrón de expresiones regulares.

Entonces estoy buscando algo como esto:

func matchesForRegexInText(regex: String!, text: String!) -> [String] {
   ???
}

Entonces esto es lo que tengo:

func matchesForRegexInText(regex: String!, text: String!) -> [String] {

    var regex = NSRegularExpression(pattern: regex, 
        options: nil, error: nil)

    var results = regex.matchesInString(text, 
        options: nil, range: NSMakeRange(0, countElements(text))) 
            as Array<NSTextCheckingResult>

    /// ???

    return ...
}

El problema es que eso matchesInStringme entrega una matriz de NSTextCheckingResult, donde NSTextCheckingResult.rangees de tipo NSRange.

NSRangees incompatible con Range<String.Index>, por lo que me impide usartext.substringWithRange(...)

¿Alguna idea de cómo lograr esto simple rápidamente sin demasiadas líneas de código?

mitchkman avatar Jan 01 '70 08:01 mitchkman
Aceptado

Incluso si el matchesInString()método toma a Stringcomo primer argumento, funciona internamente con NSString, y el parámetro de rango debe proporcionarse utilizando la NSStringlongitud y no la longitud de la cadena Swift. De lo contrario, fallará en el caso de "grupos de grafemas extendidos", como "banderas".

A partir de Swift 4 (Xcode 9), la biblioteca estándar de Swift proporciona funciones para convertir entre Range<String.Index> y NSRange.

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range($0.range, in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Ejemplo:

let string = "🇩🇪€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

Nota: El desenvolvimiento forzado Range($0.range, in: text)!es seguro porque se NSRangerefiere a una subcadena de la cadena dada text. Sin embargo, si desea evitarlo, utilice

        return results.flatMap {
            Range($0.range, in: text).map { String(text[$0]) }
        }

en cambio.


(Respuesta anterior para Swift 3 y versiones anteriores :)

Por lo tanto, debes convertir la cadena Swift dada en NSStringy luego extraer los rangos. El resultado se convertirá automáticamente en una matriz de cadenas Swift.

(El código de Swift 1.2 se puede encontrar en el historial de ediciones).

Rápido 2 (Xcode 7.3.1):

func matchesForRegexInText(regex: String, text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text,
                                            options: [], range: NSMakeRange(0, nsString.length))
        return results.map { nsString.substringWithRange($0.range)}
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Ejemplo:

let string = "🇩🇪€4€9"
let matches = matchesForRegexInText("[0-9]", text: string)
print(matches)
// ["4", "9"]

Rápido 3 (Xcode 8)

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let nsString = text as NSString
        let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: $0.range)}
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Ejemplo:

let string = "🇩🇪€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Martin R avatar Jan 10 '2015 20:01 Martin R

Mi respuesta se basa en las respuestas dadas, pero hace que la coincidencia de expresiones regulares sea más sólida al agregar soporte adicional:

  • Devuelve no sólo coincidencias sino que también devuelve todos los grupos de captura para cada coincidencia (ver ejemplos a continuación)
  • En lugar de devolver una matriz vacía, esta solución admite coincidencias opcionales
  • Evita do/catchal no imprimir en la consola y hace uso de la guardconstrucción.
  • Se agrega matchingStringscomo una extensión aString

Rápido 4.2

//: Playground - noun: a place where people can play

import Foundation

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.range(at: $0).location != NSNotFound
                    ? nsString.substring(with: result.range(at: $0))
                    : ""
            }
        }
    }
}

"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

veloz 3

//: Playground - noun: a place where people can play

import Foundation

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.rangeAt($0).location != NSNotFound
                    ? nsString.substring(with: result.rangeAt($0))
                    : ""
            }
        }
    }
}

"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

veloz 2

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matchesInString(self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.rangeAtIndex($0).location != NSNotFound
                    ? nsString.substringWithRange(result.rangeAtIndex($0))
                    : ""
            }
        }
    }
}
Lars Blumberg avatar Oct 14 '2016 10:10 Lars Blumberg

La forma más rápida de devolver todas las coincidencias y capturar grupos en Swift 5

extension String {
    func match(_ regex: String) -> [[String]] {
        let nsString = self as NSString
        return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
            (0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
        } ?? []
    }
}

Devuelve una matriz bidimensional de cadenas:

"prefix12suffix fix1su".match("fix([0-9]+)su")

devoluciones...

[["fix12su", "12"], ["fix1su", "1"]]

// First element of sub-array is the match
// All subsequent elements are the capture groups
Ken Mueller avatar Jun 16 '2019 07:06 Ken Mueller

Si desea extraer subcadenas de una Cadena, no solo la posición (sino la Cadena real, incluidos los emojis). Entonces, la siguiente quizás sea una solución más sencilla.

extension String {
  func regex (pattern: String) -> [String] {
    do {
      let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
      let nsstr = self as NSString
      let all = NSRange(location: 0, length: nsstr.length)
      var matches : [String] = [String]()
      regex.enumerateMatchesInString(self, options: NSMatchingOptions(rawValue: 0), range: all) {
        (result : NSTextCheckingResult?, _, _) in
        if let r = result {
          let result = nsstr.substringWithRange(r.range) as String
          matches.append(result)
        }
      }
      return matches
    } catch {
      return [String]()
    }
  }
} 

Uso de ejemplo:

"someText 👿🏅👿⚽️ pig".regex("👿⚽️")

Devolverá lo siguiente:

["👿⚽️"]

Nota: el uso de "\w+" puede producir un "" inesperado

"someText 👿🏅👿⚽️ pig".regex("\\w+")

Devolverá esta matriz de cadenas

["someText", "️", "pig"]
Mike Chirico avatar Nov 06 '2015 13:11 Mike Chirico

Descubrí que, lamentablemente, la solución de la respuesta aceptada no se compila en Swift 3 para Linux. Entonces aquí hay una versión modificada que hace:

import Foundation

func matches(for regex: String, in text: String) -> [String] {
    do {
        let regex = try RegularExpression(pattern: regex, options: [])
        let nsString = NSString(string: text)
        let results = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: $0.range) }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Las principales diferencias son:

  1. Swift en Linux parece requerir eliminar el NSprefijo en objetos Foundation para los cuales no existe un equivalente nativo de Swift. (Consulte la propuesta de evolución Swift n.° 86 ).

  2. Swift en Linux también requiere especificar los optionsargumentos tanto para la RegularExpressioninicialización como para el matchesmétodo.

  3. Por alguna razón, convertir a Stringen an NSStringno funciona en Swift en Linux, pero inicializar a new NSStringcon a Stringcomo fuente sí funciona.

Esta versión también funciona con Swift 3 en macOS/Xcode con la única excepción de que debes usar el nombre NSRegularExpressionen lugar de RegularExpression.

Rob Mecham avatar Oct 17 '2016 18:10 Rob Mecham