29680


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

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

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

[code]

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


CC BY-SA 3.0
2016-10-18T16:23:03.320
29675
2016-10-18T23:40:13.780
18443
question
swift swift3
文字列に指定のワードが何個含まれるかカウントしたい


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

## 自分でカウントする方法

[code]

    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
    
[/code]

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

## 正規表現を使う方法

[code]

    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
    
[/code]

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

* * *

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

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

[code]

    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
    
[/code]

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


CC BY-SA 3.0
2016-10-18T21:02:08.410
29680
2016-10-18T22:17:34.663
2016-10-18T22:17:34.663
13972
13972
29675
answer


Foundation frameworkにあるメソッド`func enumerateSubstrings(in range:
Range<String.Index>, options opts: String.EnumerationOptions = default, _
body: (String?, Range<String.Index>, Range<String.Index>, inout Bool) ->
())`を使ってみてはいかがですか？このメソッドの元は、`NSString`クラスにあるので、詳細は`NSString`のリファレンスにあたってみるといいでしょう。

[code]

    import Foundation
    // import UIKitでも可。
    
    let theString = "林檎を食べた。林檎は美味しかった。林檎が大好き"
    let subString = "林檎"
    var count = 0
    theString.enumerateSubstrings(in: theString.startIndex..<theString.endIndex, options: .byWords){
        str, _, _, _ in
        if let str = str, str == subString {
            count += 1
        }
    }
    print("\(count)") // 出力：3
    
[/code]

ポイントは、引数`options`に、`.byWords`（単語単位で列挙）を指定することです。（ただ、日本語のように、単語間をスペースで区切らない言語では、どこまで意図どおりに単語を識別してくれるか、疑問が残ります。）


CC BY-SA 3.0
2016-10-18T23:40:13.780
29681
2016-10-18T23:40:13.780
18540
29675
answer