Количество вхождений подстроки в строку в Swift

Моя основная строка - "привет Swift Swift и Swift", а подстрока - Swift. Мне нужно получить количество раз, когда подстрока "Swift" встречается в указанной строке.

Этот код может определить, существует ли шаблон.

var string = "hello Swift Swift and Swift"

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

Теперь мне нужно знать номер вхождения.

Ответы

Ответ 1

Простой подход состоит в том, чтобы разделить на "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")

Ответ 2

Я бы рекомендовал расширение для строки в 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
    }
}

Это цикл, который находит и удаляет каждый экземпляр stringToFind, увеличивая количество в каждом раунде. Как только searchString больше не содержит stringToFind, цикл прерывается и возвращается счет.

Обратите внимание, что я использую .diacriticInsensitive, поэтому игнорирует акценты (например, résume и resume будут найдены). Вы можете добавить или изменить параметры в зависимости от типов строк, которые вы хотите найти.

Ответ 3

Оптимизация решения dwsolbergs для быстрого подсчета. Также быстрее, чем 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, и он будет выполнять только точные совпадения.

Ответ 4

Если вы хотите посчитать символы, а не подстроки:

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

Ответ 5

Мне нужен способ подсчета подстрок, который может содержать начало следующей подстроки. Используя расширение dwsolbergs и диапазон строк (из: options: range: locale:), я придумал это расширение строки

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
    }
}

Ответ 6

Попробуйте это

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)

Ответ 7

Мое решение может быть, будет лучше использовать String.Index вместо диапазона Int, но я думаю, что так будет легче читать.

Строка расширения {

    func count(of char: Character, range: (Int, Int)? = nil) -> Int {
        let range = range ?? (0, self.count)

        return self.enumerated().reduce(0) {
            guard ($1.0 >= range.0) && ($1.0 < range.1) else { return $0 }
            return ($1.1 == char) ? $0 + 1 : $0
        }
    }
}