文字列に指定のワードが何個含まれるかカウントしたい

投稿者: Anonymous

文字列のなかに指定のワードがいくつ含まれるか数えたい。

例:「林檎を食べた。林檎は美味しかった。林檎が大好き」
上の文字列を「林檎」で検索したとき、3と返ってくる方法。

現在はこのメソッドを使って検索していますが、これだと有る/無ししか分からず、ワードの数までは把握できません。

if str.lowercaseString.containsString("林檎") { // -> true
}

解決

残念ながら、そう言ったこと専用のメソッドはSwiftの標準ライブラリにも、String型のFoundation拡張の中にも見つかりません。

自分でカウントする方法

let str = "林檎を食べた。林檎は美味しかった。林檎が大好き"
let word = "林檎"

var count = 0
var nextRange = str.startIndex..<str.endIndex //最初は文字列全体から探す
while let range = str.range(of: word, options: .caseInsensitive, range: nextRange) { //.caseInsensitiveで探す方が、lowercaseStringを作ってから探すより普通は早い
    count += 1
    nextRange = range.upperBound..<str.endIndex //見つけた単語の次(range.upperBound)から元の文字列の最後までの範囲で次を探す
}
print(count) //->3

Swiftの文字列のIndex(や、それを使ったRange)について慣れていないと少しわかりにくいかもしれません。

正規表現を使う方法

let pattern = NSRegularExpression.escapedPattern(for: word)
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
print(regex.numberOfMatches(in: str, range: NSRange(0..<str.utf16.count))) //->3

こちらの方が直接「数える」ためのメソッドを用意してくれているんですが、escapedPattern(for:)が必要だったり、範囲をNSRange(0..<str.utf16.count)のようにUTF-16ベースで指定しないといけないなど、少しクセがあります。NSRegularExpressionのインスタンス作成は少々「重い」処理なので、そこら辺もデメリットです。


ネットでは単語で文字列を分割して、分割後の要素数を数えるなんて裏技(?)も紹介されていましたが、「数を数える」だけのために使いもしない沢山の部分文字列と配列を作成するということでお勧めできないので、ここでは紹介しないでおきます。(見た目はそちらの方が短く書けます。)

同じような処理をちょくちょく使うのであれば、Stringの拡張として定義してしまえば良いでしょう。

extension String {
    func numberOfOccurrences(of word: String) -> Int {
        var count = 0
        var nextRange = self.startIndex..<self.endIndex
        while let range = self.range(of: word, options: .caseInsensitive, range: nextRange) {
            count += 1
            nextRange = range.upperBound..<self.endIndex
        }
        return count
    }
}
print("林檎を食べた。林檎は美味しかった。林檎が大好き".numberOfOccurrences(of: "林檎")) //->3

ネーミングはSwift3流にしてみましたが、ご自分のアプリ内で使うだけのメソッドなら、もう少し短くしても良いでしょう。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *