Avatar ·

Извлечение совпадений регулярных выражений в Swift

Всем привет! Я новичок в Swift и пытаюсь извлечь данные из строки с помощью регулярных выражений. У меня есть строка вида: "Привет, меня зовут Иван, мне 25 лет, я живу в Москве". Я хочу вытащить имя, возраст и город отдельно. В других языках (например, Python) я использовал `re.findall()`, но в Swift всё иначе. Я пробовал использовать `NSRegularExpression`, но запутался. Вот мой код: ```swift let text = "Привет, меня зовут Иван, мне 25 лет, я живу в Москве" let pattern = "Иван|25|Москва" let regex = try? NSRegularExpression(pattern: pattern) let matches = regex?.matches(in: text, range: NSRange(location: 0, length: text.utf16.count)) for match in matches ?? [] { print((text as NSString).substring(with: match.range)) } ``` Это работает, но только если я жёстко задаю значения. А мне нужно динамически извлекать данные: имя (любое слово после "зовут"), возраст (любое число после "мне"), город (любое слово после "в"). Я пробовал писать паттерн с группами захвата, например `"зовут (\\w+), мне (\\d+), в (\\w+)"`, но не понимаю, как получить эти группы из `NSTextCheckingResult`. Подскажите, как правильно извлечь группы захвата в Swift? Есть ли более современный способ без `NSRegularExpression`? Заранее спасибо! <<>>

Решение с NSRegularExpression и группами захвата

В Swift для извлечения групп захвата из регулярных выражений используется класс NSRegularExpression в паре с NSTextCheckingResult. Вот полное решение вашей задачи:

import Foundation

let text = "Привет, меня зовут Иван, мне 25 лет, я живу в Москве"
let pattern = "зовут (\\w+), мне (\\d+), в (\\w+)"

do {
    let regex = try NSRegularExpression(pattern: pattern)
    let nsRange = NSRange(text.startIndex..<text.endIndex, in: text)
    
    if let match = regex.firstMatch(in: text, range: nsRange) {
        // Извлекаем группы захвата
        let nameRange = Range(match.range(at: 1), in: text)!
        let ageRange = Range(match.range(at: 2), in: text)!
        let cityRange = Range(match.range(at: 3), in: text)!
        
        let name = String(text[nameRange])
        let age = String(text[ageRange])
        let city = String(text[cityRange])
        
        print("Имя: \(name), Возраст: \(age), Город: \(city)")
    }
} catch {
    print("Ошибка регулярного выражения: \(error.localizedDescription)")
}
// Вывод: Имя: Иван, Возраст: 25, Город: Москве

Пояснение ключевых моментов

  • Группы захвата: В паттерне используются круглые скобки () для создания групп. Нумерация групп начинается с 1 (группа 0 — всё совпадение целиком).
  • Экранирование: В Swift-строках обратный слеш экранируется двойным слэшем: \\w вместо \w.
  • NSRange vs Range: NSTextCheckingResult работает с NSRange, но мы конвертируем его в Swift Range<String.Index> для безопасной работы с String.
  • Метод firstMatch: Используем firstMatch, если нужно только первое совпадение. Для всех совпадений применяйте matches.

Обработка нескольких совпадений

Если строка содержит несколько записей, используйте matches:

let text = "Иван 25, Мария 30, Петр 40"
let pattern = "(\\w+) (\\d+)"

do {
    let regex = try NSRegularExpression(pattern: pattern)
    let matches = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
    
    for match in matches {
        let name = String(text[Range(match.range(at: 1), in: text)!])
        let age = String(text[Range(match.range(at: 2), in: text)!])
        print("\(name): \(age)")
    }
} catch {
    print(error)
}
// Вывод:
// Иван: 25
// Мария: 30
// Петр: 40

Современный Swift 5.7+ (Regex literals)

Начиная с Swift 5.7, появился встроенный тип Regex с поддержкой литералов:

import RegexBuilder

let text = "Привет, меня зовут Иван, мне 25 лет, я живу в Москве"

let regex = /зовут (\w+), мне (\d+), в (\w+)/

if let match = text.firstMatch(of: regex) {
    let (_, name, age, city) = match.output
    print("Имя: \(name), Возраст: \(age), Город: \(city)")
}
// Вывод: Имя: Иван, Возраст: 25, Город: Москве

Также можно использовать RegexBuilder для более сложных паттернов:

import RegexBuilder

let nameRef = Reference(String.self)
let ageRef = Reference(String.self)
let cityRef = Reference(String.self)

let regex = Regex {
    "зовут "
    Capture(as: nameRef) { OneOrMore(.word) }
    ", мне "
    Capture(as: ageRef) { OneOrMore(.digit) }
    ", в "
    Capture(as: cityRef) { OneOrMore(.word) }
}

if let match = text.firstMatch(of: regex) {
    print("Имя: \(match[nameRef]), Возраст: \(match[ageRef]), Город: \(match[cityRef])")
}

Рекомендация: Для проектов на Swift 5.7+ используйте Regex литералы — это чище и безопаснее. Для обратной совместимости остаётся NSRegularExpression.

<<>>

Альтернативный подход: разбиение строки и компоненты

Если ваша задача не требует сложных регулярных выражений, можно обойтись стандартными методами String и Scanner. Это часто проще для новичков.

Решение с Scanner

import Foundation

let text = "Привет, меня зовут Иван, мне 25 лет, я живу в Москве"
let scanner = Scanner(string: text)

// Пропускаем "Привет, меня зовут "
_ = scanner.scanUpToString("зовут ")
scanner.scanString("зовут ")

let name = scanner.scanUpToString(", мне") ?? ""
scanner.scanString(", мне ")

let age = scanner.scanInt() ?? 0
_ = scanner.scanString(" лет, я живу в ")

let city = scanner.scanUpToString("") ?? ""

print("Имя: \(name), Возраст: \(age), Город: \(city)")

Решение с разделением строки

let text = "Привет, меня зовут Иван, мне 25 лет, я живу в Москве"
let parts = text.components(separatedBy: ", ")

if parts.count >= 3 {
    let namePart = parts[1] // "меня зовут Иван"
    let agePart = parts[2]  // "мне 25 лет"
    let cityPart = parts[3] // "я живу в Москве"
    
    let name = namePart.components(separatedBy: " ").last ?? ""
    let age = agePart.components(separatedBy: " ")[1]
    let city = cityPart.components(separatedBy: " ").last ?? ""
    
    print("Имя: \(name), Возраст: \(age), Город: \(city)")
}

Эти методы менее гибкие, чем регулярные выражения, но для простых и стабильных форматов данных они работают отлично и не требуют изучения синтаксиса regex.