在Swift2中如何查找一个字符串在另一个字符串中的所有位置?

10

我可以使用以下代码在myString "ATGGACGTGAGCTGATCGATGGCTGAAATGAAAA"中找到字符串“ATG”的第一个位置(即索引范围为0..<3)。问题是如何找到myString中所有的“ATG”位置,而不仅仅是第一个。

let stringRange = myString.rangeOfString("ATG")
4个回答

13
您可以使用NSRegularExpression来查找字符串的所有出现: Swift 1.2:
let mystr = "ATGGACGTGAGCTGATCGATGGCTGAAATGAAAA"
let searchstr = "ATG"
let ranges: [NSRange]

// Create the regular expression.
if let regex = NSRegularExpression(pattern: searchstr, options: nil, error: nil) {
    // Use the regular expression to get an array of NSTextCheckingResult.
    // Use map to extract the range from each result.
    ranges = regex.matchesInString(mystr, options: nil, range: NSMakeRange(0, count(mystr))).map {$0.range}

} else {
    // There was a problem creating the regular expression
    ranges = []
}

println(ranges)  // prints [(0,3), (18,3), (27,3)]

Swift 2:

let mystr = "ATGGACGTGAGCTGATCGATGGCTGAAATGAAAA"
let searchstr = "ATG"
let ranges: [NSRange]

do {
    // Create the regular expression.
    let regex = try NSRegularExpression(pattern: searchstr, options: [])

    // Use the regular expression to get an array of NSTextCheckingResult.
    // Use map to extract the range from each result.
    ranges = regex.matchesInString(mystr, options: [], range: NSMakeRange(0, mystr.characters.count)).map {$0.range}
}
catch {
    // There was a problem creating the regular expression
    ranges = []
}

print(ranges)  // prints [(0,3), (18,3), (27,3)]

Swift 3: 使用 Swift 原生的 Range 类型。

let mystr = "ATGGACGTGAGCTGATCGATGGCTGAAATGAAAA"
let searchstr = "ATG"

do {
    // Create the regular expression.
    let regex = try NSRegularExpression(pattern: searchstr, options: [])

    // Use the regular expression to get an array of NSTextCheckingResult.
    // Use map to extract the range from each result.
    let fullStringRange = mystr.nsRange(from: mystr.startIndex ..< mystr.endIndex)          
    let matches = regex.matches(in: mystr, options: [], range: fullStringRange)
    let ranges = matches.map {$0.range}
    print(ranges)  // prints [(0,3), (18,3), (27,3)]
}
catch {}

注意:

  • 这种方法有其局限性。如果要搜索的字符串是简单文本,则不会有问题,但如果字符串包含在正则表达式中具有特殊意义的符号(例如"+*()[].{}?\^$"),则它将不能按预期工作。您可以预处理搜索字符串以添加转义字符来抵消这些字符的特殊含义,但这可能比价值更高。
  • mystr"AAAA"searchstr"AA" 时,另一个限制可以演示出来。在这种情况下,该字符串仅被找到两次。中间的 AA 不会被找到,因为它以属于第一个范围的字符开头。

这是一个很好的干净利落的NSRegularExpression使用方法(投票)。然而,ranges变量的作用域是否仅限于if let(Swift 1.2)或do(Swift 2)的大括号内部,而无法在外部访问?为了使其有用,我认为您需要将ranges定义为类型为[NSRange]?的数组,并将其定义在外部作用域。 - Duncan C
为什么不编辑您的答案以反映更改?这对于OP和未来读者都更有帮助。另一个小问题:在您的第三个项目符号中,您建议添加var ranges = [NSRange]()。这将分配一个新的、空的NSRange对象数组,然后被调用matchesInString返回的数组所替换。创建一个被替换的空范围没有意义。最好使用var ranges = [NSRange]!将其设置为隐式解包可选项,但不要创建空数组。这可以节省一个可抛弃的对象创建。 - Duncan C
好的,你必须以某种方式处理错误情况。当你知道它们是好的时,你可以直接使用范围(我的原始版本),你可以给“ranges”一个默认值(我的第二个版本),你可以使用可选项并检查它,或者在错误情况下为“ranges”分配一个值(我的最新版本)。 - vacawama
这在不使用变音符号的语言中有效,例如在希伯来语中使用所有变音符号,如果您尝试查找长段落末尾的内容,则无法找到它,因此最好使用mystr.unicodeScalars.count来计算单个Unicode字符而不仅仅是字形。 - sharshi

5
extension String {
    public func rangesOfString(searchString:String, options: NSStringCompareOptions = [], searchRange:Range<Index>? = nil ) -> [Range<Index>] {
        if let range = rangeOfString(searchString, options: options, range:searchRange) {

            let nextRange = Range(start:range.endIndex, end:self.endIndex)
            return [range] + rangesOfString(searchString, searchRange: nextRange)
        } else {
            return []
        }
    }
}

1
为了让它正确计算在 "aaaa" 中的 "aa" 的数量,请将下一行代码更改为:let nextRange = range.startIndex.advancedBy(1)..<self.endIndex - Sean Vikoren

1

这是有道理的,因为根据文档rangeOfString:的说明:

查找并返回接收器中给定字符串的第一个匹配项的范围。

如果您想要查找所有出现的情况,可以循环直到rangeOfString:返回nil,并且每次都将字符串修剪到与匹配范围相同。当然,您必须在原始字符串中跟踪您的位置并转置索引。


2
截取字符串是低效的。(因为它在运行时会创建一系列新的字符串对象。)最好使用接受搜索范围的rangeOfString版本,如我的答案所述。 - Duncan C

1

欢迎来到SO。

这将是一个很好的编程练习。我建议您将其作为学习项目进行。

编写一个函数,该函数接受要搜索的字符串和要搜索的字符串,并返回可选的NSRange对象数组。如果找不到任何匹配项,则可选项将为nil。或者,您可以始终返回一个数组,但如果未找到字符串,则将其包含0个NSRange对象。

让您的函数使用NSString方法rangeOfString:options:range:来搜索字符串。首先,您将搜索整个源字符串。一旦找到第一个匹配项,您将调整范围参数,以仅在该匹配项之后搜索源字符串的剩余部分。

编辑:

一种优雅的方法是将其作为String类的扩展来完成。这样,您可以像使用String的内置功能一样使用新方法。


24
这不是对问题的答案。 - Sean Vikoren
1
@SeanVikoren,我不同意。我提供了一个解决方案的大纲。实际上,我解释了如何做到这一点。我认为如果给予完整的解决方案,OP将从自己实施解决方案中学到更多。 - Duncan C
4
重点是尽可能直接地回答问题。请参见:http://stackoverflow.com/help/how-to-answer - Sean Vikoren
@SeanVikoren,引用自该链接:“任何能让提问者朝着正确方向前进的答案都是有帮助的,但请尽量在您的答案中提及任何限制、假设或简化。简洁明了是可以接受的,但更详细的解释更好。”我详细概述了一种方法,并包括了解决方案中要使用的函数信息。我没有提供代码,因为我认为这会导致复制粘贴编程而不是学习,无论是对于OP还是其他后来者都是如此。这是一个“授人以鱼不如授人以渔”的事情。 - Duncan C
@SeanVikoren,你说“回答问题应该尽可能直接明了”,我恭敬地不同意。提供现成的代码往往会削弱学习效果。这会引导人们变成“Stack Overflow战士”,只会复制粘贴网络上找到的代码,并将其拼凑成程序,而并不真正理解他们使用的代码。 - Duncan C

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