C#
正規表現
VisualBasic

【.NET】末尾数字を取得する

はじめに

これは、Visual Basic Advent Calendar 2017の10日目の記事となります。

末尾数字を取得するって結構ありそうな気がするんですが、ネット上でもっと見つかると思ったのですが意外となかったです。
文字列の語尾が数字の部分を正規表現で取得したい - teratail

コントロール名やDBの列名の末尾に数字を付けるとかありますよね。その場合は区切り文字として"_"等を付ければ、正規表現で末尾数字の取得は簡単そうです。
でも、今回は区切り文字は付けないで末尾数字を取得します。

作成の経緯

設定ファイルをxml形式で作成したところ、ユーザーからIni形式で慣れているので変更して欲しいという要望がありました。
既に本体プログラムはReadXmlでDataSet型にして設定を読み込むむようになっていたため、本体プログラムに出来るだけ影響しないようにIni形式をDataSet型に変換するクラスを作成しました。
このクラスを作成した際に、セクションやキーの末尾に数字があればグループとして扱うようにしました。

sample.ini
; フォント情報
[FONT1]
FontType = Meiryo UI
FontSize = 36
FontColor = white
[FONT2]
FontType = MS ゴシック
FontSize = 24
FontColor = red
[FONT3]
FontType = Century Gothic
FontSize = 12
FontColor = green

DataSet型にはテーブル名「FONT」でDataTable型として次のようにセットされます。Grp列にはセクションの末尾数字をセットします。

Grp FontType FontSize FontColor
1 Meiryo UI 36 white
2 MS ゴシック 24 red
3 Century Gothic 12 green

末尾数字の取得

末尾数字を取得するのに正規表現を使います。
どうせ正規表現を使うなら、英字部分(FONT)と数字部分(n)を分割して1度に取得したいと考えました。

考察 その1

正規表現の部分だけを考察します。
次の正規表現では、末尾1桁の数字なら正常に取得できるのですが、2桁以上になると英字部分に数字が含まれてしまいます。

pattern1.vb
Dim value As String = "FONT12"
Dim reg As Regex = New Regex("(?<Key>.*)(?<No>\d+$)")

'結果
key=FONT1, no=2

考察 その2

後ろから検索してくれれば、2桁以上になっても英字部分に数字が含まれないようになりそうです。
正規表現は先頭から検索していくよなーという思い込みがあって、じゃー文字列を反転させてしまえばどうだろうか。

文字列を反転されていることを考慮して、正規表現側を数字と英字の順にします。
取得結果も反転した状態なので、最終結果では反転させて元に戻します。

pattern2.vb
Dim reg As Regex = New Regex("(?<No>\d+)(?<Key>.*)")
pattern2.vb
Function Reverse(ByVal s As String) As String
    Dim chars As Char() = s.ToCharArray()
    Array.Reverse(chars)
    Return New String(chars)
End Function

Sub Main()
    Dim value As String = Reverse("FONT12")
    Dim reg As Regex = New Regex("(?<No>\d+)(?<Key>.*)")
    Dim mat As Match = reg.Match(value)
    Dim no As String = Reverse(mat.Result("${No}"))
    Dim key As String = Reverse(mat.Result("${Key}"))
    Console.WriteLine(String.Format("key={0}, no={1}", key, no))
End Sub

'結果
Reverse : 21TNOF
key = TNOF, no = 21,

key=FONT, no=12

結果はいいですが、コスト的にさすがにこれはないよな・・・

考察 その3

正規表現で後ろから読んでくれればいいよねと、検索してみると次のサイトを見つけまして、内容の結果は違えど解決のヒントがありました。
Perlの正規表現で後ろからマッチさせる

初期状態では、最長マッチとなっているので最短マッチ(「*」の後に「?」を追加)にしてみました。

pattern1.vb
Dim value As String = "FONT12"
Dim reg As Regex = New Regex("(?<Key>.*?)(?<No>\d+$)")

'結果
key=FONT, no=12

試しに、英字部分の間に数字含めてみました。
結果はいい感じです。

pattern1.vb
Dim value As String = "FO56NT123"
Dim reg As Regex = New Regex("(?<Key>.*?)(?<No>\d+$)")

'結果
key=FO56NT, no=123

最終結果

【2017/12/11追記】
albireoさんからコメントを頂き、(?<Key>.*\D)(?<No>\d+$)の方が一般的だと思います。数字のみのセクションやキーは考慮しないことにします。

sample.vb
Imports System.Text.RegularExpressions

Dim value As String = "FONT12"
Dim reg As Regex = New Regex("(?<Key>.*\D)(?<No>\d+$)")
Dim mat As Match = reg.Match(value)
Dim key As String = mat.Result("${Key}")
Dim no As String = mat.Result("${No}")
Console.WriteLine(String.Format("key={0}, no={1}", key, no))

'結果
key=FONT, no=12

C#版も載せておきます。

sample.cs
using System.Text.RegularExpressions;

string value = "FONT12";
Regex reg = new Regex(@"(?<Key>.*\D)(?<No>\d+$)");
Match mat = reg.Match(value);
string key = mat.Result("${Key}");
string no = mat.Result("${No}");
Console.WriteLine(string.Format("key={0}, no={1}", key, no));

//結果
key=FONT, no=12

最後に

実はこの記事を書くまで、次のようにしていました。
なので、お仕事のソースコードは書き換えですね。

Regex reg = new Regex("(?<No>[0-9]+$)");
Match mat = reg.Match(value);
string no = mat.Result("${No}");
string key = value.Remove(value.LastIndexOf(no), no.Length);