在Swift中,字符串中子串出现的次数

72

我的主字符串是"hello Swift Swift and Swift",子字符串是Swift。 我需要获取在提到的字符串中子字符串"Swift"出现的次数。

这段代码可以确定模式是否存在。

var string = "hello Swift Swift and Swift"

if string.rangeOfString("Swift") != nil {
    println("exists")
}

现在我需要知道出现的次数。

12个回答

118
一个简单的方法是按照 "Swift" 进行分割,然后从部分数量中减去1:
let s = "hello Swift Swift and Swift"
let tok =  s.components(separatedBy:"Swift")
print(tok.count-1)

这段代码打印出3。

编辑:Swift 3语法之前,代码看起来像这样:

let tok =  s.componentsSeparatedByString("Swift")

36

如果您想要计算字符而不是子字符串:

extension String {
    func count(of needle: Character) -> Int {
        return reduce(0) {
            $1 == needle ? $0 + 1 : $0
        }
    }
}

22

Swift 5 扩展

extension String {
    func numberOfOccurrencesOf(string: String) -> Int {
        return self.components(separatedBy:string).count - 1
    }
}

使用示例

let string = "hello Swift Swift and Swift"
let numberOfOccurrences = string.numberOfOccurrencesOf(string: "Swift")
        
// numberOfOccurrences = 3

如果您不需要不区分大小写的搜索,那么这是一个不错的小技巧。 - Andy Dent

21

优化 dwsolbergs solution 以更快地进行计数,也比 componentsSeparatedByString 更快。

extension String {
    /// stringToFind must be at least 1 character.
    func countInstances(of stringToFind: String) -> Int {
        assert(!stringToFind.isEmpty)
        var count = 0
        var searchRange: Range<String.Index>?
        while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
            count += 1
            searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
        }
        return count
    }
}

使用方法:

// return 2
"aaaa".countInstances(of: "aa")
  • 如果你想忽略音调,可以将options: []替换为options: .diacriticInsensitive,就像dwsolbergs所做的那样。
  • 如果你想忽略大小写,可以将options: []替换为options: .caseInsensitive,就像ConfusionTowers建议的那样。
  • 如果你既想忽略音调又想忽略大小写,可以将options: []替换为options: [.caseInsensitive, .diacriticInsensitive],就像ConfusionTowers所建议的那样。
  • 另一方面,如果您希望进行最快速的比较,并且可以保证组合字符序列的某些规范形式,则可以考虑选项.literal,它只会执行精确匹配。

1
一个非常好的解决方案,特别是通过将选项更改为 [.caseInsensitive, .diacriticInsensitive],它可以找到搜索词而不考虑大小写。太棒了! - ConfusionTowers

17

我建议在Swift 3中增加字符串扩展,例如:

extension String {
    func countInstances(of stringToFind: String) -> Int {
        var stringToSearch = self
        var count = 0
        while let foundRange = stringToSearch.range(of: stringToFind, options: .diacriticInsensitive) {
            stringToSearch = stringToSearch.replacingCharacters(in: foundRange, with: "")
            count += 1
        }
        return count
    }
}

这是一个循环,它会查找并删除每个字符串实例,并在每次循环中递增计数。一旦搜索字符串不再包含任何待查找的字符串,循环就会终止并返回计数。

请注意,我使用.diactricInsensitive以忽略重音(例如résume和resume都可以被找到)。您可能需要根据要查找的字符串类型添加或更改选项。


1
使用 replaceSubrange 可能比 replacingCharacters 更好,以避免每次创建新字符串。此外,使用 range(of:options:range:) 可能比 range(of:options:) 更好,以避免每次从 startIndex 开始搜索。 - Cœur
我已经在我的回答中应用了所描述的优化。 - Cœur

4

我需要一种方法来计算可能包含下一个匹配子字符串开头的子字符串的数量。利用dwsolbergs的扩展和Strings range(of:options:range:locale:)方法,我想出了这个String扩展。

extension String
{
    /**
     Counts the occurrences of a given substring by calling Strings `range(of:options:range:locale:)` method multiple times.

     - Parameter substring : The string to search for, optional for convenience

     - Parameter allowOverlap : Bool flag indicating whether the matched substrings may overlap. Count of "" in "" is 2 if allowOverlap is **false**, and 3 if it is **true**

     - Parameter options : String compare-options to use while counting

     - Parameter range : An optional range to limit the search, default is **nil**, meaning search whole string

     - Parameter locale : Locale to use while counting

     - Returns : The number of occurrences of the substring in this String
     */
    public func count(
        occurrencesOf substring: String?,
        allowOverlap: Bool = false,
        options: String.CompareOptions = [],
        range searchRange: Range<String.Index>? = nil,
        locale: Locale? = nil) -> Int
    {
        guard let substring = substring, !substring.isEmpty else { return 0 }

        var count = 0

        let searchRange = searchRange ?? startIndex..<endIndex

        var searchStartIndex = searchRange.lowerBound
        let searchEndIndex = searchRange.upperBound

        while let rangeFound = range(of: substring, options: options, range: searchStartIndex..<searchEndIndex, locale: locale)
        {
            count += 1

            if allowOverlap
            {
                searchStartIndex = index(rangeFound.lowerBound, offsetBy: 1)
            }
            else
            {
                searchStartIndex = rangeFound.upperBound
            }
        }

        return count
    }
}

3
为什么不直接使用一些长度数学?
extension String {
    func occurences(of search:String) -> Int {
        guard search.count > 0 else {
            preconditionFailure()
        }

        let shrunk = self.replacingOccurrences(of: search, with: "")

        return (self.count - shrunk.count)/search.count
    }
}

1

试试这个

var mainString = "hello Swift Swift and Swift"
var count = 0

mainString.enumerateSubstrings(in: mainString.startIndex..<mainString.endIndex, options: .byWords) { (subString, subStringRange, enclosingRange, stop) in

    if case let s? = subString{

        if s.caseInsensitiveCompare("swift") == .orderedSame{
            count += 1
        }
    }


}

print(count)

1
另一种方法是在iOS 16+和swift 5.7+中使用RegexBuilder。
import RegexBuilder

let text  = "hello Swift Swift and Swift"
let match = text.matches(of: Regex{"Swift"})
print(match.count) // prints 3

将其用作函数

func countSubstrings(string : String, subString : String)-> Int{
    return string.matches(of: Regex{subString}).count
}

print(countSubstrings(string: text, subString: "Swift")) //prints 3

将其用作扩展

extension String {
    
    func countSubstrings(subString : String)-> Int{
        return self.matches(of: Regex{subString}).count
    }
}

print(text.countSubstrings(subString: "Swift")) // prints 3

1

为了完整性和因为有一个regex标签,这是一个使用正则表达式的解决方案。

let string = "hello Swift Swift and Swift"
let regex = try! NSRegularExpression(pattern: "swift", options: .caseInsensitive)
let numberOfOccurrences = regex.numberOfMatches(in: string, range: NSRange(string.startIndex..., in: string))

选项 .caseInsensitive 是可选的。

如果原始字符串具有正则表达式特殊字符,那么这种方法会失败,但其意图是执行简单的字符串搜索。 - Andy Dent

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