

記述するべき内容が不足していたら申し訳ありません。

  

* * *

### やりたいこと

配列内を **正規表現を用いて** 検索し、「マッチした文字列」の中で「一番要素数が大きい文字列」を取得したいです。

  

* * *

### 現状

「文字列」(=inData、可変)と「区切文字」(=delimiter、可変、正規表現を使う場合あり)を投げると、

[code]

    ・Regex.splitを用いて区切る
    ・「区切前方文字(=front)」を代入
    ・「マッチした区切文字(delimiter)」を代入
    ・「区切後方文字(=behind)」を代入
    ・区切れたら(inData内からdelimiterがマッチする箇所が1つでも存在したら)true/区切れなかったらfalseを返す
    
[/code]

上記のことを行う関数(GetSplitData)を作りました。

「区切文字にマッチする箇所が1箇所のみだった」時の動きの例は以下の通りです。  
これは想定通り動作しています。

[code]

    例：GetSplitData("AAA BBB", "\s+", "", "")// inData, delimiter, front, behind
    戻り値→inData = "AAA BBB"
          front = "AAA"
          delimiter = " "
          behind = "BBB"
          return true
    
[/code]

今回「区切り文字にマッチする箇所が複数存在した」時の動きを悩んでいます。  
望む形は以下の通りです。

[code]

    例：GetSplitData("AAA00BBB0000CCC", "[0-9]+", "", "")
      →inData = "AAA00BBB0000CCC"
       front = "AAA00BBB"
       delimiter = "0000"
       behind = "CCC"
       return true
    
[/code]

  

Regex.splitを用いている為、現在の動きだと  
Stringの配列内に"AAA","00","BBB","0000","CCC"が入ります。  
ここから後ろの"0000"を取得する方法が思いつきません。  
複数存在したら再度配列をdelimiterで舐めようかと思ったのですが、  
**配列を正規表現を用いて舐める方法** が検索しても出てきませんでした。  
また、後ろのdelimiterが取得出来た所で、  
その **前後を取得する方法** も思いつきません。  
(正規表現なしならLastIndexOfが使えそうだなとは思いましたが…。使うとどうも"[0-9]+"という文字列を検索しに行ってしまう？)  
(私が調べ損ねているだけで、LastIndexOfでも正規表現が使えるのでしょうか？)

使えそうなメソッド、アルゴリズム、考え方等ございましたらご教授願います。  
皆様のお知恵をお貸しいただければと思います。  
どうぞよろしくお願いいたします。

  

### 環境

・Microsoft Visual Studio 2013 Express for Windows  
・.NET Framework 4.5.1  
・C# 5.0

  

* * *

### 追記

混乱させてしまい申し訳ございません。  
delimiterの採用条件は「文字列の長さ」ではなく「一番後ろにあるもの」です。  
"AAA0000BBB00CCC"というinData、"[0-9]+"というdelimiterが来た場合、  
delimiterに採用するのは「00」です。一番後ろにある為。  
array[x]のxのことを「要素数」と勘違いしておりました。  
用語を正しく使わなかった為に混乱させてしまい、大変失礼いたしました。

  

* * *

### 追記２ そもそもこの質問をすることになった経緯

[code]

    public bool GetSplitData(string inData, string delimiter, ref string front, ref string behind)
    {
        // 区切文字は後ろから検索する
        int index = inData.IndexOf(delimiter);
        // 区切り無し
        if (index == -1) return false;
    
        front = inData.Substring(0, index);
        behind = inData.Substring(index + delimiter.Length, inData.Length - (index + delimiter.Length));
        return true;
    }
    
[/code]

  

  1. この関数は「○○課 山田 太郎」のような文字列を区切る為に使用していた
  2. 最初は上記のコードを使って区切っていた(そもそもindexOfは「最初に見つけた部分のindexを返す関数」のはずなのに何故か最後の部分のindexを返してくれる。未だに謎です。)
  3. 「1.」の時はデリミターに「課」を入力させ、別の関数で「前にくっつける」ことを行い、「○○課」「山田 太郎」と区切っていた
  4. 後になって「○○課\r\n山田 太郎」や「○○課○○部 山田太郎」というのが出てきた
  5. indexOfに"\r\n"や"(課|部)"を入れても区切ってくれない
  6. 「区切文字がマッチする箇所が複数存在したら一番後ろで区切る」という仕様が決められている
  7. 質問に至る


CC BY-SA 3.0
2017-08-31T00:18:30.100
37628
2018-03-15T00:15:16.517
2017-09-01T00:11:09.567
question
c# .net 正規表現
配列内を正規表現で検索したい


`LastIndexOf`のように検索したいのか文字数で判断したいのかよくわかりませんが、`Regex.Matches`を呼び出して最長または最後`Match`を採用すればよいのでは。

[code]

    var ms = Regex.Matches(inData, delimiter);
    if (ms.Count > 0)
    {
        var m = ms[mc.Count -1];
    
        front = inData.Substring(0, m.Index);
        delimiter = m.Value;
        behind = inData.Substring(m.Index + m.Length);
    
        return true;
    }
    
[/code]

## 正規表現で否定先読みを使用すべきではない理由

正規表現を複数の箇所にマッチさせる場合、特定の箇所にマッチしたかどうか自体が後続の結果に影響します。ですので正規表現自体を書き換えるのは避けるべきです。

### 例

文字列`aaba`にパターン`(a+|aba)`をマッチさせた場合、該当しうる箇所は

  1. 1 文字目: `a`
  2. 1-2文字目: `aa`
  3. 2 文字目: `a`
  4. 2-4文字目: `aba`
  5. 4 文字目: `a`

です。しかし`Regex.Matches`でマッチさせた場合、結果として`aa`(2.)と`a`(5.)が得られます。  
これは`aa`に含まれる1.と、`aa`の途中から始まる3.と4.が除外されたからです。

しかし否定先読みで`(a+|aba)(?!.*(a+|aba))`とパターンを変えると、1.と2.は4文字目の`a`のためにマッチしません。ですので2文字目から始まる3.と4.が比較され、より長い`aba`がマッチします。

もちろん「`a`も`aba`も終了位置は同じ」と開き直ることもできますが、`GetSplitData`メソッドの実装を知らない人になぜ`a`ではなく`aba`がデリミターなのかという仕様を説明することは難しいでしょう。  
単純に`Regex.Matches`を使用すれば正規表現の基本ルールにのっとっていますので、第三者へのわかりやすさからこちらをお勧めします。

## 例2

否定先読みを使うと問題になるもっとわかりやすいケースがあったので結果だけ掲載しておきます。原因は上で説明した通りです。

  * `Regex.Split("a==b==c", "==")` -> `"a", "b", "c"`
  * `Regex.Split("a====c", "==")` -> `"a", "", "c"`
  * `Regex.Split("a====c", "==(?!.*==)")` -> `"a=", "=c"`


CC BY-SA 3.0
2017-08-31T01:24:31.387
37630
2017-08-31T22:44:05.053
2017-08-31T22:44:05.053
5750
5750
37628
answer


sayuri様、pgrho様、ご回答ありがとうございました。  
今回は先に回答していただいたsayuri様をベストアンサーとさせていただきます。  
pgrho様のラムダ式、linqを用いた書き方も非常に勉強になりました。別の箇所で参考にさせていただきます。

以下、実際に動かしたプログラムです。

  

[code]

    public bool GetSplitData(string inData, ref string delimiter, ref string front, ref string behind)
    {
        // 否定先読みの正規表現に置き換える
        string pattern = String.Format("({0}(?!.*{0}))", delimiter);
        Regex splitRegex = new Regex(pattern);
        // SplitでinDataを区切る
        string[] splited = splitRegex.Split(inData);
        // 3つに区切れたら振り分ける
        if (splited.Length == 3)
        {
            front = splited[0];
            delimiter = splited[1];
            behind = splited[2];
            return true;
        }
        else return false;
    }
    
[/code]


CC BY-SA 3.0
2017-08-31T07:44:44.377
37637
2017-08-31T07:44:44.377
37628
answer