PHPの正規表現で文字列を文単位に分割する方法
はじめに
PHPでテキストを文に分割する場合、単純にピリオドや感嘆符で区切るだけでは不十分です。「Dr.」「Mr.」「Prof.」といった敬称、「e.g.」「etc.」などの略語、月名の省略形(Jan. や Feb.)などはピリオドを含みますが、文の終わりではありません。
本記事で紹介する sentence_split() 関数は、文の境界の「直前」と「直後」に対応する正規表現ペアを多数用意し、それらを順番に照合することで、こうした例外ケースも考慮した高精度な文分割を実現します。
サンプルコード
function sentence_split($text) {
$before_regexes =
array('/(?:(?:[\'\"\„][\.!?\…][\'\"\”]\s)|(?:[^\.]\s[A-Z]\.\s)|(?:\b(?:St|Gen|Hon|Prof|Dr|Mr|Ms|Mrs|[JS]r|Col|Maj|Brig|Sgt|Capt|Cmnd|Sen|Rev|Rep|Revd)
\.\s)|(?:\b(?:St|Gen|Hon|Prof|Dr|Mr|Ms|Mrs|[JS]r|Col|Maj|Brig|Sgt|Capt|Cmnd|Sen|Rev|Rep|Revd)\.\s[A-Z]\.\s)|(?:\bApr\.\s)|(?:\bAug\.\s)|(?:\bBros\.
\s)|(?:\bCo\.\s)|(?:\bCorp\.\s)|(?:\bDec\.\s)|(?:\bDist\.\s)|(?:\bFeb\.\s)|(?:\bInc\.\s)|(?:\bJan\.\s)|(?:\bJul\.\s)|(?:\bJun\.\s)|(?:\bMar\.\s|(?
:\bNov\.\s)|(?:\bOct\.\s)|(?:\bPh\.?D\.\s)|(?:\bSept?\.\s)|(?:\b\p{Lu}\.\p{Lu}\.\s)|(?:\b\p{Lu}\.\s\p{Lu}\.\s)|(?:\bcf\.\s)|(?:\be\.g\.\s)|(?:\besp
\.\s)|(?:\bet\b\s\bal\.\s)|(?:\bvs\.\s)|(?:\p{Ps}[!?]+\p{Pe} ))\Z/su',
'/(?:(?:[\.\s]\p{L}{1,2}\.\s))\Z/su',
'/(?:(?:[\[\(]*\.\.\.[\]\)]* ))\Z/su',
'/(?:(?:\b(?:pp|[Vv]iz|i\.?\s*e|[Vvol]|[Rr]col|maj|Lt|[Ff]ig|[Ff]igs|[Vv]iz|[Vv]ols|[Aa]pprox|[Ii]ncl|Pres|[Dd]ept|min|max|[Gg]ovt|lb|ft|c\.?\s
*f|vs)\.\s))\Z/su',
'/(?:(?:\b[Ee]tc\.\s))\Z/su',
'/(?:(?:[\.!?\…]+\p{Pe} )|(?:[\[\(]*\…[\]\)]* ))\Z/su',
'/(?:(?:\b\p{L}\.))\Z/su',
'/(?:(?:\b\p{L}\.\s))\Z/su',
'/(?:(?:\b[Ff]igs?\.\s)|(?:\b[nN]o\.\s))\Z/su',
'/(?:(?:[\"\”\']\s*))\Z/su',
'/(?:(?:[\.!?\…]
[\x{00BB}\x{2019}\x{201D}\x{203A}\"\'\p{Pe}\x{0002}]*\s)|(?:\r?\n))\Z/su',
'/(?:(?:[\.!?\…]
[\'\"\x{00BB}\x{2019}\x{201D}\x{203A}\p{Pe}\x{0002}]*))\Z/su',
'/(?:(?:\s\p{L}[\.!?\…]\s))\Z/su');
$after_regexes = array('/\A(?:)/su',
'/\A(?:[\p{N}\p{Ll}])/su',
'/\A(?:[^\p{Lu}])/su',
'/\A(?:[^\p{Lu}]|I)/su',
'/\A(?:[^p{Lu}])/su',
'/\A(?:\p{Ll})/su',
'/\A(?:\p{L}\.)/su',
'/\A(?:\p{L}\.\s)/su',
'/\A(?:\p{N})/su',
'/\A(?:\s*\p{Ll})/su',
'/\A(?:)/su',
'/\A(?:\p{Lu}[^\p{Lu}])/su',
'/\A(?:\p{Lu}\p{Ll})/su');
$is_sentence_boundary = array(false, false, false, false, false, false, false, false, false, false, true, true, true);
$count = 13;
$sentences = array();
$sentence = '';
$before = '';
$after = substr($text, 0, 10);
$text = substr($text, 10);
while($text != '') {
for($i = 0; $i < $count; $i++) {
if(preg_match($before_regexes[$i], $before) && preg_match($after_regexes[$i], $after)) {
if($is_sentence_boundary[$i]) {
array_push($sentences, $sentence);
$sentence = '';
}
break;
}
}
$first_from_text = $text[0];
$text = substr($text, 1);
$first_from_after = $after[0];
$after = substr($after, 1);
$before .= $first_from_after;
$sentence .= $first_from_after;
$after .= $first_from_text;
}
if($sentence != '' && $after != '') {
array_push($sentences, $sentence.$after);
}
return $sentences;
}
$text = "Hello there, hello from Tokyo, Japan, Universe, Earth.";
print_r(sentence_split($text));実行結果
上記のコードを実行すると、次のような出力が得られます。
Array ( [0] => Hello there, hello from Tokyo, Japan, Universe, Earth. )
仕組みの解説
この関数は、入力テキストを先頭から1文字ずつ順番に走査していきます。処理のどの時点においても、テキストは常に2つの部分に分かれています。ひとつは文の境界よりも前にある部分($before)、もうひとつは境界よりも後にある部分($after)です。
$before_regexes には、閉じ括弧付きの記号、人名のイニシャル、St. や Dr. などの敬称、月名の省略形、cf. や e.g. などのラテン語由来の略語といった、ピリオドを含むものの文末ではないパターンが13種類定義されています。一方、$after_regexes には、境界の直後に続く文字のパターン(大文字で始まる単語、数字、小文字など)が定義されています。
走査中の各時点で、これらの正規表現ペアが順に照合されます。前方と後方の両方のパターンが一致しても、$is_sentence_boundary のフラグが false のうちは文の区切りとは見なされず、そのまま次の文字へ進みます。これは、略語や敬称のピリオドを文末と誤認しないための仕組みです。
そして、最後の3組の正規表現ペア(「[.!?…] + 引用符や括弧 + 空白または改行」などのパターン)で一致した場合に初めて、そこが文の境界であると判定されます。このとき、それまでに蓄積された文字列が1つの文として $sentences 配列に追加されます。
このように、境界の前後双方の文脈を組み合わせて段階的に判定することで、「U.S.A.」のような頭字語や「No. 5」のような表記など、誤検出が起きやすい箇所でも正確に文の区切りを識別できるのが特徴です。
-
C#で文字列を文字列配列の要素に分割する方法
C#では、Split()メソッドを使うことで、1つの文字列を簡単に複数の要素(文字列配列)に分割できます。この記事では、基本的な使い方から実際のコード例まで、わかりやすく解説します。手順1:分割したい文字列を用意するまず、分割対象となる文字列を変数に格納します。string str = Hello World!;手順2:Split()メソッドで文字列を分割する次に、Split()メソッドを使って、区切り文字(ここでは半角スペース)を指定し、文字列を個別の要素に分割します。結果は文字列配列として返されます。string[] res = str.Split( );完全なサンプルコード以下は、C#で
-
C#で正規表現を使って文字列から空白を削除する方法
C#では、正規表現(Regex)を利用することで、文字列内の不要な空白を簡単に削除できます。本記事では、System.Text.RegularExpressions 名前空間の Regex.Replace メソッドを使って、文字列から空白を取り除く方法を解説します。削除対象の文字列まず、次のような空白を含む文字列 str1 を用意します。string str1 = Brad Pitt;Regex.Replaceで空白を置き換えるRegex.Replace メソッドを使い、空白(\s+)を空文字列()に置き換えます。\s は半角スペースやタブなど任意の空白文字にマッチし、+ によって連続した空白