文字列を指定文字列を含む形で分割

やりたいこと

指定文字列をTargetとした時
Targetx⇒{ Target, x }
xTarget⇒{ x, Target }
のような配列を作成する。

切り出しの開始位置を求める

まずは単純にするために指定文字列をAとして
分割する文字列をxAxxAとする。
指定文字列の開始位置はString.IndexOf("A", 開始位置);で検索する。
処理順は以下のように考えられる。

  1. 先頭から指定文字列を検索する。
  2. 見つかった文字列の次から検索する。(繰り返し)

探索開始位置をstartPositionとすると、上記の処理は以下のようになる。

  1. startPosition = 0;
  2. startPosition = String.IndexOf("A", startPosition) + 指定文字列の長さ;

次に終了条件(OR)を考える。

  • 対象の文字列には指定文字列がない
  • 最後まで探索した

String.IndexOfで指定文字列が存在しない場合は-1を返す。
以上より、終了条件は以下のいずれかになる。

  • String.IndexOf("A", startPosition) == -1
  • startPostion == 文字列の長さ

以上よりコードは以下のようになる。

public static List<int> GetStrPatternStartIndexes(string str, string pattern)
{
  var ret = new List<int>();
  
  var strLen = str.Length;
  var patternLen = pattern.Length;
  var startPosition = 0; // 先頭から指定文字列を検索
  while(startPosition < strLen) // 最後まで検索するまで
  {
    var index = str.IndexOf(pattern, startPosition);
    if (index == -1) break; // 見つからない
    
    ret.Add(index);
    startPosition = index + patternLen; // 見つかった文字列の次から検索
  }
  return ret;
}

実際に分割する

先ほどの例より、分割対象の開始位置は分かった。
切り出しには、String.Substring(開始位置, 文字数);を使用する。
しかし、このままでは以下のように、対象文字列の前後の文字は含まれない。
xAxxAx⇒{ A, A }

対象文字列より前にある文字

先ほどのコードでいうとstartPositionとindexが異なる場合は、
startPotionから"indexから(-)startPotionまで"を切り出す必要がある。

最後尾の対象文字列以降の文字

上の処理を実行しても、2つ目のAのあとの5つ目のxを切り出せない。
つまり最後に見つからないときに、startPositionからすべて切り出す必要がある。

以上を考慮すると、以下の通りになる。

public static List<string> SplitStrByPattern(string str, string pattern)
{
  var ret = new List<string>();
  
  var strLen = str.Length;
  var patternLen = pattern.Length;
  var startPosition = 0; // 先頭から指定文字列を検索
  while(startPosition < strLen) // 最後まで検索するまで
  {
    var index = str.IndexOf(pattern, startPosition);
    if (index == -1)// 見つからない
    {
      ret.Add(str.Substring(startPosition)); // 末尾の文字列
      break; 
    }
    
    if (index != startPosition)
      ret.Add(str.Substring(startPosition, index - startPostion));
    ret.Add(str.Substring(index, patternLength));
    startPosition = index + patternLen; // 見つかった文字列の次から検索
  }
  return ret;
}