[Swift3.0] 正規表現にマッチした回数や文字列を得る

2016年8月16日(更新: 2017年12月20日)

Swiftで正規表現を使って文字列内を検索して特定の文字列を切り出す方法です。

Swiftでの正規表現の扱いは NSRange などを使用する必要があるので少し複雑です。

マッチした回数を得るメソッド

    // マッチした数を返す
    func getMatchCount(targetString: String, pattern: String) -> Int {
        
        do {
            
            let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
            let targetStringRange = NSRange(location: 0, length: (targetString as NSString).length)
            
            return regex.numberOfMatches(in: targetString, options: [], range: targetStringRange)
            
        } catch {
            print("error: getMatchCount")
        }
        return 0
    }

このメソッドは第一引数に正規表現で検索する対象となる文字列を、第二引数に正規表現パターンを受け取ります。

NSRegularExpression

正規表現を扱うには、まず NSRegularExpression のインスタンスを用意します。

NSRegularExpression はパターンが無効だった場合などに例外を発生させるため、do-catch構文を利用してエラーを処理する必要があります。

NSRegularExpression の第一引数に正規表現パターンを、第二引数にオプションを配列の形で渡します(この例では、大文字小文字の区別を無視するオプション .caseInsensitive を指定しています)

numberOfMatchesInString

実際にマッチした数を数えるのは NSRegularExpression のメソッド numberOfMatchesInString です。

このメソッドの第三引数には、検索範囲を NSRange で渡す必要があります。今回は対象文字列の最初から最後までを検索範囲としたいので、検索対象の全範囲を含んだ NSRange を作ってそれを渡しています。

このとき、Stringクラスの targetString.characters.count を使わず (targetString as NSString).length のように、NSStringに変換してから文字列の長さを取得しているのには理由があります。

Stringクラスの characters.count は文字列の文字を、全角半角関係なく1文字と数えます。一方 NSStringクラスの length は、全角文字を半角文字2文字分として数えます

もし検索対象文字列に日本語や絵文字などの全角文字が含まれていた場合、characters.count では正しい長さを取得できません。

マッチした部分を配列に格納して返すメソッド

    // 正規表現にマッチした文字列を格納した配列を返す
    func getMatchStrings(targetString: String, pattern: String) -> [String] {

        var matchStrings:[String] = []
        
        do {
            
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let targetStringRange = NSRange(location: 0, length: (targetString as NSString).length)
            
            let matches = regex.matches(in: targetString, options: [], range: targetStringRange)
            
            for match in matches {
                
                // rangeAtIndexに0を渡すとマッチ全体が、1以降を渡すと括弧でグループにした部分マッチが返される
                let range = match.rangeAt(0)
                let result = (targetString as NSString).substring(with: range)
                
                matchStrings.append(result)
            }
            
            return matchStrings
            
        } catch {
            print("error: getMatchStrings")
        }
        return []
    }

NSRegularExpression を使うことや、 NSRange で検索範囲を指定するのは先ほどと同じです。

matchesInString

マッチした部分の情報を配列の形で返すNSRegularExpression のメソッドです。帰ってくるのは純粋な文字列ではなく、NSTextCheckingResult クラスとしてなので、ここから文字列だけを抽出する必要があります。

rangeAtIndex

このメソッドで、マッチした文字列が文字列全体のうちのどの部分かを NSRange の形で得ることができます。

その次の行で文字列を NSString に変換し、先ほど得た NSRange で文字列を切り出します。文字列から一部を切り出すには substring を使います。

NSString に変換する理由は、String型のままでは NSRange での切り出しが行えないことと、前述したように全角文字が含まれていた際に範囲がおかしくなってしまうのを防ぐためです。

グループ部分の切り出し

パターンの中にグループ(カッコで囲んだ部分)がある場合は rengeAtIndex の引数に1以上を渡すことで、グループ部分だけの NSRange を取得できます。

もし、グループも含めたマッチ部分を取得したい場合は、ループ部分を以下のように変更します。

...
            
for match in matches {
                
    // rangeAtIndexに0を渡すとマッチ全体が、1以降を渡すと括弧でグループにした部分マッチが返される
    for groupNum in 0 ..< match.numberOfRanges {
        let range = match.rangeAt(groupNum)
        let result = (targetString as NSString).substring(with: range)
        matchStrings.append(result)
    }
}
            
...

使用例

Xcodeで新規プロジェクトを作り、そのViewControllerを以下のように変更してテストしました。

ソースコード全体を以下に記載します。

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 対象の文字列
        let targetString = "absodihogeoaispogejihadfoostestaiodaosj"
        
        // 正規表現パターン
        let matchPattern = "hoge|foo|a.?s"
        
        // マッチした数を表示
        let matchCount = getMatchCount(targetString: targetString, pattern: matchPattern)
        print(matchCount)
        
        // マッチした文字列をすべて表示
        let matches = getMatchStrings(targetString: targetString, pattern: matchPattern)
        for str in matches {
            print(str)
        }
    }
    // マッチした数を返す
    func getMatchCount(targetString: String, pattern: String) -> Int {
    
        do {
            
            let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
            let targetStringRange = NSRange(location: 0, length: (targetString as NSString).length)
            
            return regex.numberOfMatches(in: targetString, options: [], range: targetStringRange)
            
        } catch {
            print("error: getMatchCount")
        }
        return 0
    }

    // 正規表現にマッチした文字列を格納した配列を返す    
    func getMatchStrings(targetString: String, pattern: String) -> [String] {

        var matchStrings:[String] = []
        
        do {
            
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let targetStringRange = NSRange(location: 0, length: (targetString as NSString).length)
            
            let matches = regex.matches(in: targetString, options: [], range: targetStringRange)
            
            for match in matches {
                
                // rangeAtIndexに0を渡すとマッチ全体が、1以降を渡すと括弧でグループにした部分マッチが返される
                let range = match.rangeAt(0)
                let result = (targetString as NSString).substring(with: range)
                
                matchStrings.append(result)
            }
            
            return matchStrings
            
        } catch {
            print("error: getMatchStrings")
        }
        return []
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

文字列 foo, bar, aとsの間に一文字挟んだ文字列を検索パターンとしています。

結果は以下のようになります。

5
abs
hoge
ais
foo
aos

正規表現にマッチした回数とマッチした部分の文字列が出力されているのが確認できます。

コメントを残す

メールアドレスが公開されることはありません。