在Swift中获取字符串中子串的所有范围

12

我有一个字符串,例如 "ab ad adk fda kla kad ab ab kd"。我想获取所有“ab”的范围。(在这里,“ab”出现在第3个位置,所以我应该得到3个范围)。在正常情况下,我的代码可以正常工作,但如果搜索文本是“.”,那么我会得到错误的结果。

do {
    let regEx = try NSRegularExpression(pattern: searchText, options: NSRegularExpressionOptions.CaseInsensitive)

    let matchesRanges = regEx.matchesInString(attributedText.string, options:[], range: NSMakeRange(0, attributedText.string.length))

    for rng in matchesRanges {
        let wordRange = rng.rangeAtIndex(0)
    }
} catch {
    ...
}

你需要展示你所尝试的,并解释它哪里出了问题。 - Wain
请详细解释你想要什么结果? - PSS
一个 . 是一个特殊字符,表示“任何字符”。 - redent84
你知道正则表达式是什么吗?这不是简单的逐个字符的字符串搜索... - Eiko
5个回答

21

以下解决方案使用本地的 Swift 4 函数 range(of:, options:, range:, locale:)

extension String {
    func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
        var ranges: [Range<Index>] = []
        while ranges.last.map({ $0.upperBound < self.endIndex }) ?? true,
            let range = self.range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex, locale: locale)
        {
            ranges.append(range)
        }
        return ranges
    }
}

Swift 4 提供了本地 API,可将 Range<Index> 转换为 NSRange


2
在某些情况下,使用正则表达式可能会导致无限循环。请查看此答案以了解如何避免 https://dev59.com/g1wY5IYBdhLWcg3ws5oF#32306142 - Leo Dabus
@LeoDabus,我已经找到了一个可能发生这种情况的案例,因为确实有一些正则表达式会返回有效的范围,即使要搜索的范围为空。我已经更新了代码以反映这一点。如果这不是您最初遇到的问题,您能否分享导致问题的正则表达式? - Stéphane Copin
如果我没记错的话,这种情况发生在字符串末尾有一个空范围的情况下。抱歉,但我找不到相关的问题/正则表达式。 - Leo Dabus
@LeoDabus 我明白了,没问题,我所做的修改应该足以解决这个问题了 ;) - Stéphane Copin

18

Swift 5:

最受欢迎的答案的改进版本:

extension String {    
    func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
        var ranges: [Range<Index>] = []
        while let range = range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex, locale: locale) {
            ranges.append(range)
        }
        return ranges
    }
}

4
我建议这样的解决方案:
import Foundation

extension String {

    func rangesOfString(s: String) -> [Range<Index>] {
        let re = try! NSRegularExpression(pattern: NSRegularExpression.escapedPatternForString(s), options: [])
        return re.matchesInString(self, options: [], range: nsRange(startIndex ..< endIndex)).flatMap { range($0.range) }
    }

    func range(nsRange : NSRange) -> Range<Index>? {
        let utf16from = utf16.startIndex.advancedBy(nsRange.location, limit: utf16.endIndex)
        let utf16to   = utf16from.advancedBy(nsRange.length, limit: utf16.endIndex)

        if let from = String.Index(utf16from, within: self),
           let to   = String.Index(utf16to,   within: self)
        {
            return from ..< to
        } else {
            return nil
        }
    }

    func nsRange(range : Range<Index>) -> NSRange {
        let utf16from = String.UTF16View.Index(range.startIndex, within: utf16)
        let utf16to   = String.UTF16View.Index(range.endIndex,   within: utf16)
        return NSRange(location: utf16.startIndex.distanceTo(utf16from), length: utf16from.distanceTo(utf16to))
    }

}

print("[^x]? [^x]? [^x]?".rangesOfString("[^x]?")) // [Range(0..<5), Range(6..<11), Range(12..<17)]

除了主要问题之外,此代码还展示了如何根据这篇帖子NSRange转换为Range<String.Index>以及从Range<String.Index>转换回NSRange


它是否处理所有其他特殊字符? - user1865227
我使用NSRegularExpression.escapedPatternForString()来转义任何可能的模式元字符。这是一个可靠的解决方案。 - werediver
那些范围<->NSRange的转换看起来很熟悉https://dev59.com/QF8e5IYBdhLWcg3w_-r0#30404532 :) - Martin R
那可能是正确的来源。我已经在帖子中添加了参考。 - werediver

1
您正在使用正则表达式,因此需要注意具有特殊含义的字符 -“.”只是其中之一。
如果您正在搜索子字符串,则建议改用传统的rangeOf...方法:
func rangeOfString(_ searchString: String,
           options mask: NSStringCompareOptions,
             range searchRange: NSRange) -> NSRange

只需不断调用该方法并调整searchRange,直到找不到更多匹配项为止。


0
您可以通过以下代码获取特定字符串的出现次数:
let str: NSMutableString = "ab ad adk fda kla kad ab ab kd"
let count = str.replaceOccurrencesOfString("ab", withString: "ab", options: NSStringCompareOptions.LiteralSearch, range: NSMakeRange(0, str.length))

3
不,我想获取范围,这样就可以在这些范围上应用属性。 - user1865227

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