MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift正则表达式与文本处理

2024-08-015.8k 阅读

Swift 正则表达式基础

在Swift编程中,正则表达式(Regular Expression)是一种强大的工具,用于处理文本模式匹配和搜索。正则表达式使用一种专门的语法来定义文本模式,这种语法可以用来查找、替换或提取符合特定模式的文本。

在Swift中,正则表达式的处理主要依赖于 NSRegularExpression 类,它是Foundation框架的一部分。尽管Swift是一种较新的编程语言,但它充分利用了Objective - C的Foundation框架的强大功能来处理正则表达式。

创建NSRegularExpression实例

要在Swift中使用正则表达式,首先需要创建一个 NSRegularExpression 实例。可以使用 NSRegularExpression 的初始化方法来创建实例,这个初始化方法接受一个正则表达式模式字符串和一些选项参数。

let pattern = "hello"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    // 这里regex就是创建好的NSRegularExpression实例
} catch {
    print("创建正则表达式失败: \(error)")
}

在上面的代码中,pattern 是正则表达式的模式字符串,这里简单地设置为 "hello"。try NSRegularExpression(pattern: pattern, options: []) 尝试使用给定的模式和选项创建一个 NSRegularExpression 实例。如果创建过程中出现错误,例如模式字符串语法不正确,会通过 catch 块捕获并打印错误信息。

正则表达式选项

NSRegularExpression 的初始化方法接受一个选项参数,这个参数可以用来修改正则表达式的行为。一些常用的选项包括:

  • .caseInsensitive:忽略大小写匹配。例如,模式 "hello" 在这个选项下也会匹配 "HELLO"、"Hello" 等。
  • .anchorsMatchLines:让 ^ 和 $ 锚点匹配行的开始和结束,而不仅仅是字符串的开始和结束。
  • .dotMatchesLineSeparators:让 . 字符匹配包括换行符在内的所有字符。默认情况下,. 不匹配换行符。

下面是一个使用忽略大小写选项的例子:

let pattern = "hello"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
    // 这里的regex会忽略大小写进行匹配
} catch {
    print("创建正则表达式失败: \(error)")
}

使用正则表达式进行匹配

创建好 NSRegularExpression 实例后,就可以使用它来对文本进行匹配操作。NSRegularExpression 提供了几种不同的方法来进行匹配,每种方法适用于不同的场景。

查找所有匹配项

matches(in:options:range:) 方法用于在给定的字符串中查找所有符合正则表达式模式的匹配项。它返回一个 NSTextCheckingResult 数组,每个 NSTextCheckingResult 对象代表一个匹配结果。

let text = "hello world, hello Swift"
let pattern = "hello"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    let matches = regex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
    for match in matches {
        let matchRange = Range(match.range, in: text)!
        let matchString = String(text[matchRange])
        print("找到匹配项: \(matchString)")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

在上述代码中,首先定义了一个文本字符串 text 和一个正则表达式模式 pattern。然后创建 NSRegularExpression 实例,并使用 matches(in:options:range:) 方法在 text 中查找所有匹配项。NSRange(text.startIndex..., in: text) 表示在整个 text 字符串范围内进行搜索。对于每个匹配结果,通过 Range(match.range, in: text)NSTextCheckingResult 中的 NSRange 转换为Swift的 Range,从而提取出匹配的字符串并打印。

查找第一个匹配项

firstMatch(in:options:range:) 方法用于查找字符串中第一个符合正则表达式模式的匹配项。它返回一个可选的 NSTextCheckingResult 对象,如果没有找到匹配项则返回 nil

let text = "hello world, hello Swift"
let pattern = "hello"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    if let match = regex.firstMatch(in: text, options: [], range: NSRange(text.startIndex..., in: text)) {
        let matchRange = Range(match.range, in: text)!
        let matchString = String(text[matchRange])
        print("找到第一个匹配项: \(matchString)")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

这段代码与查找所有匹配项的代码类似,只是使用了 firstMatch(in:options:range:) 方法。如果找到了匹配项,就提取并打印匹配的字符串;如果没有找到,则打印提示信息。

检查是否存在匹配项

numberOfMatches(in:options:range:) 方法用于检查字符串中是否存在符合正则表达式模式的匹配项,并返回匹配项的数量。

let text = "hello world, hello Swift"
let pattern = "hello"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    let count = regex.numberOfMatches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
    if count > 0 {
        print("存在匹配项,数量为: \(count)")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

此代码通过 numberOfMatches(in:options:range:) 方法获取匹配项的数量,并根据数量判断是否存在匹配项并打印相应信息。

正则表达式语法

正则表达式的强大之处在于其丰富的语法,通过不同的字符和符号组合,可以定义各种复杂的文本模式。

基本字符匹配

最基本的正则表达式就是匹配普通字符。例如,模式 "abc" 会匹配字符串中出现的 "abc" 文本。

let text = "abcdef"
let pattern = "abc"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    if let match = regex.firstMatch(in: text, options: [], range: NSRange(text.startIndex..., in: text)) {
        print("找到匹配项")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

字符类

字符类用于匹配一组字符中的任意一个。字符类使用方括号 [] 定义。例如,[abc] 会匹配 "a"、"b" 或 "c" 中的任意一个字符。

let text = "xyzabc"
let pattern = "[abc]"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    let matches = regex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
    for match in matches {
        let matchRange = Range(match.range, in: text)!
        let matchString = String(text[matchRange])
        print("找到匹配项: \(matchString)")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

还可以定义字符范围,例如 [a - z] 匹配任意小写字母,[0 - 9] 匹配任意数字。

元字符

元字符是具有特殊含义的字符。一些常见的元字符包括:

  • .:匹配除换行符以外的任意字符。例如,模式 "a.c" 会匹配 "abc"、"a1c" 等。
  • ^:匹配字符串的开始位置。例如,模式 "^hello" 只会匹配以 "hello" 开头的字符串。
  • $:匹配字符串的结束位置。例如,模式 "world$" 只会匹配以 "world" 结尾的字符串。
  • *:匹配前面的字符零次或多次。例如,模式 "a*" 会匹配空字符串、"a"、"aa"、"aaa" 等。
  • +:匹配前面的字符一次或多次。例如,模式 "a+" 会匹配 "a"、"aa"、"aaa" 等,但不匹配空字符串。
  • ?:匹配前面的字符零次或一次。例如,模式 "a?" 会匹配空字符串或 "a"。
let text1 = "a1c"
let pattern1 = "a.c"
do {
    let regex1 = try NSRegularExpression(pattern: pattern1, options: [])
    if let match1 = regex1.firstMatch(in: text1, options: [], range: NSRange(text1.startIndex..., in: text1)) {
        print("找到匹配项")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

let text2 = "hello world"
let pattern2 = "^hello"
do {
    let regex2 = try NSRegularExpression(pattern: pattern2, options: [])
    if let match2 = regex2.firstMatch(in: text2, options: [], range: NSRange(text2.startIndex..., in: text2)) {
        print("找到匹配项")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

分组

分组使用圆括号 () 来定义。分组可以将多个字符组合成一个单元,以便对整个组应用量词或进行反向引用。例如,模式 "(abc)+" 会匹配 "abc"、"abcabc"、"abcabcabc" 等。

let text = "abcabcabc"
let pattern = "(abc)+"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    if let match = regex.firstMatch(in: text, options: [], range: NSRange(text.startIndex..., in: text)) {
        print("找到匹配项")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

使用正则表达式进行替换

除了匹配文本,正则表达式还常用于文本替换操作。NSRegularExpression 提供了 stringByReplacingMatches(in:options:range:withTemplate:) 方法来进行替换。

let text = "hello world, hello Swift"
let pattern = "hello"
let replacement = "hi"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    let newText = regex.stringByReplacingMatches(in: text, options: [], range: NSRange(text.startIndex..., in: text), withTemplate: replacement)
    print("替换后的文本: \(newText)")
} catch {
    print("创建正则表达式失败: \(error)")
}

在上述代码中,pattern 是要匹配的正则表达式模式,replacement 是替换的字符串。stringByReplacingMatches(in:options:range:withTemplate:) 方法会在 text 中查找所有匹配 pattern 的文本,并将其替换为 replacement,返回替换后的新字符串。

使用分组进行替换

当正则表达式中包含分组时,可以在替换模板中使用反向引用来引用分组匹配的内容。在替换模板中,$1 表示第一个分组,$2 表示第二个分组,以此类推。

let text = "name: John, age: 30"
let pattern = "name: (\\w+), age: (\\d+)"
let replacement = "Age: $2, Name: $1"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    let newText = regex.stringByReplacingMatches(in: text, options: [], range: NSRange(text.startIndex..., in: text), withTemplate: replacement)
    print("替换后的文本: \(newText)")
} catch {
    print("创建正则表达式失败: \(error)")
}

在这个例子中,(\\w+)(\\d+) 是两个分组,分别匹配名字和年龄。在替换模板中,$1 引用第一个分组(名字),$2 引用第二个分组(年龄),从而实现了对文本内容的重新排列。

使用正则表达式进行文本提取

正则表达式也可用于从文本中提取特定的信息。通过定义合适的正则表达式模式,并结合匹配结果的范围,可以提取出所需的文本片段。

let text = "Email: john@example.com"
let pattern = "Email: (\\S+)"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    if let match = regex.firstMatch(in: text, options: [], range: NSRange(text.startIndex..., in: text)) {
        let captureRange = Range(match.range(at: 1), in: text)!
        let email = String(text[captureRange])
        print("提取的邮箱: \(email)")
    } else {
        print("未找到匹配项")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

在上述代码中,(\\S+) 是一个分组,用于匹配邮箱地址。match.range(at: 1) 获取第一个分组的范围,通过将其转换为Swift的 Range,从而提取出邮箱地址字符串。

性能考虑

在使用正则表达式时,性能是一个需要考虑的重要因素。复杂的正则表达式模式可能会导致匹配过程变得缓慢,尤其是在处理大量文本时。

  • 简化模式:尽量简化正则表达式模式,避免不必要的分组和复杂的嵌套。例如,如果不需要对某个部分进行反向引用,就不要使用分组。
  • 预编译:创建 NSRegularExpression 实例是一个相对昂贵的操作,因此如果需要在多个地方使用相同的正则表达式,应该预编译并复用该实例,而不是每次都创建新的实例。
// 预编译正则表达式
let pattern = "hello"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    let text1 = "hello world"
    let text2 = "hello Swift"
    let matches1 = regex.matches(in: text1, options: [], range: NSRange(text1.startIndex..., in: text1))
    let matches2 = regex.matches(in: text2, options: [], range: NSRange(text2.startIndex..., in: text2))
    // 复用regex实例进行多次匹配
} catch {
    print("创建正则表达式失败: \(error)")
}

处理多行文本

在处理多行文本时,需要注意正则表达式的锚点(^$)以及 . 字符的默认行为。默认情况下,^$ 匹配整个字符串的开始和结束,而 . 不匹配换行符。

如果要让 ^$ 匹配每一行的开始和结束,可以使用 .anchorsMatchLines 选项。要让 . 匹配包括换行符在内的所有字符,可以使用 .dotMatchesLineSeparators 选项。

let multiLineText = "line1\nline2\nline3"
let pattern = "^line"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: .anchorsMatchLines)
    let matches = regex.matches(in: multiLineText, options: [], range: NSRange(multiLineText.startIndex..., in: multiLineText))
    for match in matches {
        let matchRange = Range(match.range, in: multiLineText)!
        let matchString = String(multiLineText[matchRange])
        print("找到匹配项: \(matchString)")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

在上述代码中,通过设置 .anchorsMatchLines 选项,^line 模式可以匹配每一行以 "line" 开头的内容。

常见的正则表达式应用场景

验证邮箱地址

邮箱地址的格式有一定的规范,可以使用正则表达式来验证其格式是否正确。

let email = "john@example.com"
let emailPattern = "[A - Za - z0 - 9._%+-]+@[A - Za - z0 - 9.-]+\\.[A - Za - z]{2,}"
do {
    let regex = try NSRegularExpression(pattern: emailPattern, options: [])
    if let match = regex.firstMatch(in: email, options: [], range: NSRange(email.startIndex..., in: email)) {
        print("邮箱格式正确")
    } else {
        print("邮箱格式错误")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

验证电话号码

电话号码的格式因地区而异,但可以使用正则表达式来验证常见的格式。

let phone = "123 - 456 - 7890"
let phonePattern = "\\d{3}-\\d{3}-\\d{4}"
do {
    let regex = try NSRegularExpression(pattern: phonePattern, options: [])
    if let match = regex.firstMatch(in: phone, options: [], range: NSRange(phone.startIndex..., in: phone)) {
        print("电话号码格式正确")
    } else {
        print("电话号码格式错误")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

提取URL

从文本中提取URL是另一个常见的应用场景。

let text = "Visit our website at https://www.example.com"
let urlPattern = "https?://[\\w.-]+"
do {
    let regex = try NSRegularExpression(pattern: urlPattern, options: [])
    if let match = regex.firstMatch(in: text, options: [], range: NSRange(text.startIndex..., in: text)) {
        let matchRange = Range(match.range, in: text)!
        let url = String(text[matchRange])
        print("提取的URL: \(url)")
    } else {
        print("未找到URL")
    }
} catch {
    print("创建正则表达式失败: \(error)")
}

通过合理运用正则表达式,在Swift编程中可以高效地处理各种文本相关的任务,无论是数据验证、信息提取还是文本替换等。但在实际使用中,要注意正则表达式的性能和准确性,确保其在不同场景下都能稳定工作。同时,不断学习和掌握正则表达式的语法和特性,可以进一步提升文本处理的能力。