今回はC#で正規表現を使って文字列処理をしてみたいと思います。
文字列の処理は
の記事で扱ったString型を使った処理で行う事が出来ますが、正規表現を使うと文字列のパターンで検索し文字列の処理を行う事が出来るようになります。
正規表現は便利ですが、その分意図したパターンとは違う文字列と一致させてしまう可能性もあるのでなかなか難しいですねぇ。(´Д`)
正規表現を使ってパターンが一致するか判定
正規表現はRegexクラスを使うと使用する事が出来ます。
パターンを設定してRegexのインスタンスを生成
パターンを設定してRegexクラスのインスタンスを生成する事で、対象の文字列がそのパターンと一致するかどうかを判定する事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest1 : MonoBehaviour { // Start is called before the first frame update void Start() { // 検索対象の文字列 string testString = "012あ"; // 検索パターンを設定してRegexのインスタンス生成 Regex regex = new Regex("\\d"); // パターンが一致するかどうかの判定 if(regex.IsMatch(testString)) { Debug.Log("数字に一致しました"); } else { Debug.Log("一致しませんでした。"); } regex = new Regex("あ"); if(regex.IsMatch(testString)) { Debug.Log("一致しました"); } else { Debug.Log("一致しませんでした。"); } } } |
上では\dで数字と一致するかどうかのパターンを設定しRegexクラスのインスタンスであるregexを作成し、testStringの文字列がそのパターンと一致するかどうかを判定しています。
IsMatchメソッドの戻り値はbool型なのでそのままif分の条件式に設定出来ます。
パターンが\\dとなっているのは\がエスケープ文字として判定される為に2重の\記号を書く必要があります。
\を書くのが面倒な場合は
1 2 3 | Regex regex = new Regex(@"\d"); |
と”の前に@マークを付けると2重に書かなくて済みます。
上の例では検索対象の文字列内に数字と一致するものがあれば真が返されます。
下の例では「あ」のパターンに一致するかどうかを判定していて、検索対象文字列には「あ」が含まれているので「一致しました」がコンソールに表示されます。
正規表現オプションを指定
Regexクラスのインスタンスを生成する時に正規表現オプションを指定する事も出来ます。
1 2 3 | Regex regex = new Regex(@"\d", RegexOptions.IgnoreCase); |
RegexOptions列挙型はいくつかあります。
オプション | 効果 |
---|---|
None | 既定のオプション |
IgnoreCase | 大文字と小文字を区別しない |
Multiline | 複数行モードにし、^と$は各行の行頭と行末を表すようになる(記号については後述します)。 |
Singleline | 単一行モードにします。 |
IgnorePatternWhitespace | エスケープされていない空白を排除し、#記号以降をコメントにします。 |
RightToLeft | 検索の方向を右から左に変更します。 |
Regexクラスのクラスメソッドで判定
Regexクラスのインスタンスを生成せず、Regexクラスのクラスメソッドを使ってパターンの一致を判定する事も出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest2 : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "012あ"; if(Regex.IsMatch(testString, @"\d")) { Debug.Log("数字と一致しています。"); } else { Debug.Log("数字と一致していません。"); } } } |
クラスメソッドのIsMatchを使った場合も正規表現オプションを指定する事が出来ます。
正規表現と一致した情報の取得
RegexクラスのIsMatchメソッドを使うと検索対象の文字列がパターンと一致しているかどうかを判定する事ができましたが、一致した文字列の情報は取得出来ませんでした。
RegexクラスのMatchメソッドを使うと戻り値としてMatch型が得られ、Match型のプロパティを参照することで一致した文字列情報を取得する事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest3 : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "abccba"; Match match = Regex.Match(testString, @"B", RegexOptions.IgnoreCase); Debug.Log(match.Success); Debug.Log(match.Index); Debug.Log(match.Value); Debug.Log(match.Length); Debug.Log(match.Groups[0]); } } |
上の例では大文字小文字を無視して検索文字列の中に「B」という文字列パターンと一致するものを検索しています。
コンソールには
True
1
b
1
b
と表示されます。
得られたMatch型のプロパティは
プロパティ | 得られる値 |
---|---|
Success | 一致した検索対象が存在したかどうか |
Index | 見つかった文字列の最初の位置 |
Value | 見つかった文字列 |
Length | 見つかった文字列の長さ |
Groups | 一致したグループのコレクションを取得 |
上のようになります。
他にもプロパティはあるので、参考サイトに記載した.NETのMatchクラスのプロパティを参照してください。
正規表現で使う記号
正規表現のパターンには先ほどの例のように\dといったエスケープ文字や記号を使ってパターンを作成します。
いくつかの記号を紹介します。
パターン | 一致する文字列 |
---|---|
. | 任意の一文字と一致 |
* | 0回以上の繰り返し |
+ | 1回以上の繰り返し |
? | 0回または1回に一致 |
{n} | n回の繰り返し |
{n,} | n回以上の繰り返し |
{n,m} | n回以上m回以下の繰り返し |
^ | 行頭 |
$ | 行末 |
() | ()内をグループ化しGroupsで参照できる |
\n | 前方の()の順番で\1、\2と順番に取得出来る |
[] | []内の文字のいずれかと一致 |
[^] | []内の文字以外と一致 |
[0-9] | 0から9の数字と一致 |
[a-z] | aからzまでの文字と一致 |
\d | 0から9の数字と一致 |
\s | 空白文字と一致 |
\b | 単語文字と単語文字以外の境界に一致 |
\w | 単語に使用される文字と一致 |
\p{IsHiragana} | ひらがなと一致 |
\p{IsKatakana} | カタカナと一致 |
{n,m}の記号に関しては、
{1, 4}のように4の前に空白を入れるとうまく作用されませんでした。
{2,3}のように空白を入れないほうがいいようです。
パターンに使用する記号は数多くある為、記事の最後に記載している参考サイトの.NETの正規表現のページを参照してください。
これらの記号を組み合わせることで様々な文字列パターンの検索をすることが出来ます。
正規表現の記号を使ったサンプル
正規表現の記号について見てきたので、いくつかサンプルを作成してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexSymbol : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "あいう 123 かきくけこ456\nkamekumechan@gmail.com さしすせそ\tたちつてと"; // 全部の文字 string pattern1 = @".*"; Debug.Log(Regex.Match(testString, pattern1, RegexOptions.Singleline).Value); // 数字を取得 string pattern2 = @"\s*(\d+)\s*"; Match match = Regex.Match(testString, pattern2); while(match.Success) { // ()内のデータを表示 Debug.Log(match.Groups[1]); match = match.NextMatch(); } // kamekumechanを取得 string pattern3 = @"\b[a-z]{6,}\b"; match = Regex.Match(testString, pattern3); while(match.Success) { Debug.Log(match.Value); match = match.NextMatch(); } // 5文字のひらがなを取得 string pattern4 = @"\p{IsHiragana}{5}"; match = Regex.Match(testString, pattern4); while(match.Success) { Debug.Log(match.Value); match = match.NextMatch(); } // 「あい」で始まる文字列に一致 string pattern5 = @"^あい.*"; match = Regex.Match(testString, pattern5); while(match.Success) { Debug.Log(match.Value); match = match.NextMatch(); } // 「つてと」で終わる文字列に一致 string pattern6 = @".*つてと$"; match = Regex.Match(testString, pattern6); while(match.Success) { Debug.Log(match.Value); match = match.NextMatch(); } // aからz、空白、改行、数字、@マーク、.以外の文字と一致 string pattern7 = @"[^a-z\s0-9@\.]"; match = Regex.Match(testString, pattern7); while(match.Success) { Debug.Log(match.Value); match = match.NextMatch(); } // 前方参照構成体サンプル string testString2 = "123 321 456 123 225 552"; string pattern8 = @"(\d)\s\1"; match = Regex.Match(testString2, pattern8); while(match.Success) { Debug.Log(match.Value); match = match.NextMatch(); } } } |
パターン1は該当する文字列全てと一致するのでtestStringの文字列が全て表示されます。
途中改行コード(/n)があるのでRegexOptions.Singlelineを付けて改行コードも含めて1行として取り扱います。
パターン2は\s*で空白や改行などの一文字以上の繰り返しの後、\d+で数字が1文字以上続いて、その後\s*で空白や改行等が一文字以上続く文字列を取得しています。
取得したデータの表示ではmatch.Groups[1]でグループ化して一致した文字列を表示しています。
この場合は()で\d+を囲んでいますので、\d+の部分と一致する数字の部分がコンソールに表示されます。
パターン3は「kamekumechan」という文字列を取得しますが、\bで単語文字と単語文字以外の境界を表し、その後aからzのいずれかの文字が6文字以上続いて、\bで境界がくる文字列を取得しています。
パターン4は平仮名5文字と一致する文字列を取得しています。
パターン5は「あい」で始まる文字列を取得しています。
パターン6は「つてと」で終わる文字列を取得しています。
MatchではRegexOptionsでSinglelineを指定していない為、「つてと」で終わる文字列の先頭は改行コードである\nの後の「kamekumechan]から始まります。
パターン7では[]内のパターンのいずれかと一致する文字列を返しますが、[]のパターンの先頭に^を付けている為、[]内に記述したパターン以外の文字列が一致します。
この場合ではコンソールに平仮名一文字ずつ表示されることになります。
パターン8では新しくtestString2を宣言しました。
\dに()を付け最初の一致した\dを\1を指定した部分で適用します。
最初に数字一字、その後に空白や改行、その後\1なので()内のパターンと一致させるので\dに該当する文字列が来ます。
この場合は「3 3」、「5 5」が該当します。
パターンに一致した全ての対象を取得
パターンに一致した全ての対象を取得するにはいくつか方法があります。
得られたMatchのNextMatchメソッドを使用する
Regex.Matchで得られたMatch型の値からNextMatchメソッドを使って次に一致するMatch型の値を得られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest4 : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "abccba"; Match match = Regex.Match(testString, "B", RegexOptions.IgnoreCase); while(match.Success) { Debug.Log(match.Success); Debug.Log(match.Index); Debug.Log(match.Value); Debug.Log(match.Length); Debug.Log(match.Groups[0].Value); match = match.NextMatch(); } } } |
match.Successで一致したものがあるかどうか判断し、while文内でmatch.NextMatchで次に一致するものを取得し繰り返します。
UnityコンソールのCollapseをオフにしていればコンソールに
True
1
b
1
b
True
4
b
1
b
が表示されます。
Regex.Matchesメソッドで一括で取得する
Regex.Matchesメソッドを使用するとパターンに一致するデータを取得出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest5 : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "abccba"; MatchCollection matches = Regex.Matches(testString, "B", RegexOptions.IgnoreCase); foreach (Match match in matches) { Debug.Log(match.Success); Debug.Log(match.Index); Debug.Log(match.Value); Debug.Log(match.Length); Debug.Log(match.Groups[0].Value); } } } |
Regex.Matchesで得られるデータはMatchCollection型になります。
foreachで繰り返す時に
1 2 3 | foreach (var match in matches) { |
と暗黙の型指定が出来そうですが、MatchCollection型の場合はMatchと明示しないと駄目なようです。
一致した文字列を置き換える
一致した文字列を置き換える事も出来ます。
文字列を置き換えるにはRegex.Replaceメソッドを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest6 : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "abccba"; string replaceString = Regex.Replace(testString, @"B", "d", RegexOptions.IgnoreCase); Debug.Log(replaceString); } } |
上の例では「abccba」の文字列から大文字小文字を区別せず「B]という文字パターンがあればそれを「d」という文字に変換し、それをreplaceStringに代入しています。
実行するとUnityのコンソールには
adccda
が表示されます。
指定したパターンの文字列で分割して配列にする
指定したパターンの文字列を使って検索対象の文字列を分割し、配列にする事も出来ます。
検索対象の文字列を指定したパターンの文字列で分割するにはRegex.Splitメソッドを使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class RegexTest7 : MonoBehaviour { // Start is called before the first frame update void Start() { string testString = "abccba"; string[] replaceStrings = Regex.Split(testString, @"B", RegexOptions.IgnoreCase); foreach(var replaceString in replaceStrings) { Debug.Log(replaceString); } } } |
上の例では「abccba」という検索対象の文字列を大文字小文字関係なく「B」という文字列パターンによって分割しreplaceStringsという配列を作成しています。
実行するとUnityのコンソールには
a
cc
a
が表示され、「B」を使って分割し、配列が作成されていることがわかります。
終わりに
わたくし自身のタイピングゲーム製作の為に正規表現を使う事を検討してこの記事を作成しましたが、なかなか思うように文字列パターンの指定ができませんね。(^_^;)
正規表現を使わずStringを使って問題文のローマ字への変換や入力によって自動で問題文の変換をした方がいいのかもしれません。
どういったパターンで変換すれば良いか?ここら辺はIQテストに出てくるような法則性を見つける能力が必要になってくるのかも?
0 1 1 2 3 5 8 13 21 34
といったものはどういった法則で並んでいるのか?などですね。
答えは・・・・(´Д`)
実は正規表現を使ってタイピングソフトで使うような平仮名からローマ字への変換機能も載せる予定だったんですが、その後いろいろ作っているうちに当初のアルゴリズムだと非常に不便であった為に載せるのはやめました。
タイピングの処理がうまく出来るようになったら少し記事にするかも?しれません。
参考サイト
.NET の正規表現から色々見ることが出来ます。