如何在Swift中使用正则表达式?

6

我正在使用Swift开发一个应用程序,需要从字符串中捕获8个数字。

以下是要处理的字符串:

index.php?page=index&l=99182677

我的匹配模式是:

&l=(\d{8,})

这是我的代码:

var yourAccountNumber = "index.php?page=index&l=99182677"
let regex = try! NSRegularExpression(pattern: "&l=(\\d{8,})", options: NSRegularExpressionOptions.CaseInsensitive)
let range = NSMakeRange(0, yourAccountNumber.characters.count)
let match = regex.matchesInString(yourAccountNumber, options: NSMatchingOptions.Anchored, range: range)

首先,我不知道什么是 NSMatchingOptions,在官方的苹果文库上,我没有找到所有的 .Anchored, .ReportProgress等内容。有没有人能够对此进行解释?
其次,当我执行print(match)时,似乎没有任何值存储在该变量中([])。
我正在使用带有Swift 2.0的Xcode 7 Beta 3。

没错,我需要提取那8位数字。对此很抱歉。 - Anthony
4个回答

8

原始答案

这里是一个函数,你可以利用它来获取捕获组文本:

import Foundation

extension String {
    func firstMatchIn(string: NSString!, atRangeIndex: Int!) -> String {
        var error : NSError?
        let re = NSRegularExpression(pattern: self, options: .CaseInsensitive, error: &error)
        let match = re.firstMatchInString(string, options: .WithoutAnchoringBounds, range: NSMakeRange(0, string.length))
        return string.substringWithRange(match.rangeAtIndex(atRangeIndex))
    }
}

然后:
var result = "&l=(\\d{8,})".firstMatchIn(yourAccountNumber, atRangeIndex: 1)

atRangeIndex: 1 中的 1 将提取由 (\d{8,}) 捕获组捕获的文本。

注意1:如果您计划在&l=后提取8个数字,您不需要在限定量词中使用,,因为{8,}表示8或更多。如果您只想捕获8个数字,请改为使用{8}

注意2NSMatchingAnchored 是您希望避免的内容,如果您期望的结果不在搜索范围的开头。请参阅文档

指定匹配仅限于搜索范围的开头。

注意3:谈到“最简单”的事情时,我建议您避免使用环视(look-arounds),除非您没有必要。环视通常会对性能产生一些影响,如果您不打算捕获重叠文本,我建议您使用捕获组。

SWIFT 2的更新

我想出了一个函数,它将返回所有带有所有捕获组的匹配项(类似于PHP中的preg_match_all)。以下是在您的场景中使用它的方法:

func regMatchGroup(regex: String, text: String) -> [[String]] {
do {
    var resultsFinal = [[String]]()
    let regex = try NSRegularExpression(pattern: regex, options: [])
    let nsString = text as NSString
    let results = regex.matchesInString(text,
        options: [], range: NSMakeRange(0, nsString.length))
    for result in results {
        var internalString = [String]()
        for var i = 0; i < result.numberOfRanges; ++i{
            internalString.append(nsString.substringWithRange(result.rangeAtIndex(i)))
        }
        resultsFinal.append(internalString)
    }
    return resultsFinal
   } catch let error as NSError {
       print("invalid regex: \(error.localizedDescription)")
       return [[]]
   }
}
// USAGE:
let yourAccountNumber = "index.php?page=index&l=99182677"
let matches = regMatchGroup("&l=(\\d{8,})", text: yourAccountNumber)
if (matches.count > 0) // If we have matches....
{ 
    print(matches[0][1]) //  Print the first one, Group 1.
}

1
那个也很有趣。我现在不知道该选择哪一个了。但我猜这取决于我的风格。:) 谢谢! - Anthony
3
每当有人使用扩展方法时,您似乎都会引用这个名言。我不明白自由函数(与扩展相对)和简单性之间的联系。您是在说Beck只倡导具有显式“this”的自由函数而非方法吗? - Airspeed Velocity
关于{8,},我是故意这样做的,因为未来可能会出现更多的数字。关于NOTE3,你所说的“look-arounds”是什么意思? - Anthony
1
"Look-arounds"(http://www.regular-expressions.info/lookaround.html)是指“零宽断言”...或者简称为“向前查看”或“向后查看”。 - Wiktor Stribiżew
3
扩展并不比顶层函数“更复杂”。使用扩展的情况是,您只需在值上键入一个点,并滚动浏览其类型的所有方法和扩展。因此,您的扩展更易发现,更可能被使用。如果我有一个主要目的是处理传入数据的顶层函数,我可能会将其移动到该数据类型的扩展中。此外,像方法一样链接扩展的能力通常使代码更易读。 - Rikki Gibson
显示剩余4条评论

1

使用NSString方法可能会更容易,而不是使用NSRegularExpression

var yourAccountNumber = "index.php?page=index&l=99182677"
println(yourAccountNumber) // index.php?page=index&l=99182677

let regexString = "(?<=&l=)\\d{8,}+"
let options :NSStringCompareOptions = .RegularExpressionSearch | .CaseInsensitiveSearch
if let range = yourAccountNumber.rangeOfString(regexString, options:options) {
    let digits = yourAccountNumber.substringWithRange(range)
    println("digits: \(digits)")
}
else {
    print("Match not found")
}

(?<=&l=)的意思是前置断言,但不包括其本身。

详细解释:

正则表达式中的向后查找断言。如果括号中的模式匹配当前输入位置之前的文本,并且匹配的最后一个字符是当前位置之前的输入字符,则返回true。它不改变输入位置。查找模式匹配的可能字符串长度不能无限制(不能使用*或+运算符)。

在没有证明的情况下,总体性能考虑只是过早优化。话虽如此,在正则表达式中使用前后查找会有其他有效的理由和反对理由。

ICU用户指南:正则表达式


运行良好,谢谢。然而在Swift 2.0中,println()已被print()替换。谢谢! - Anthony
在2.0版本中,let options :NSStringCompareOptions = .RegularExpressionSearch | .CaseInsensitiveSearch将变为let options: NSStringCompareOptions = [.RegularExpressionSearch, .CaseInsensitiveSearch] - Airspeed Velocity
我建议使用 let digits = range.map { yourAccountNumber[$0] },而不是强制解包,然后根据用例使用一些解包技巧。 - Airspeed Velocity
很不幸,Xcode beta 3 对我来说总是崩溃,所以我无法使用Swift 2.0。是的,强制解包真的很糟糕,我只在示例代码中使用了它,我已经更新了答案。 - zaph

1
对于Swift 2,您可以使用此String的扩展:
import Foundation

extension String {
    func firstMatchIn(string: NSString!, atRangeIndex: Int!) -> String {
        do {
            let re = try NSRegularExpression(pattern: self, options: NSRegularExpressionOptions.CaseInsensitive)
            let match = re.firstMatchInString(string as String, options: .WithoutAnchoringBounds, range: NSMakeRange(0, string.length))
            return string.substringWithRange(match!.rangeAtIndex(atRangeIndex))
        } catch {
            return ""
        }
    }
}

您可以使用以下代码获取账号:

var result = "&l=(\\d{8,})".firstMatchIn(yourAccountNumber, atRangeIndex: 1)

0

NSMatchingOptions.Anchored替换为NSMatchingOptions()(无选项)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接