文章を一文ごとに分割する正規表現

モチベイション

ある文字列を一文ごとに分割することを考える。ここで文とは句点(。)で区切られた文字の一繋がりで、その末尾以外の場所に句点は出現しないとしよう(この文自体が例外であるが)。そのような文がいくつか繋がった文字列を、文ごとに分割したいとき、まず思いつくのはこのような方法である:

text.split(/。/)

この方法は部分的に上手くゆく。すなわち文字列は分割されるが、それらの文は末尾に句点を含まない。したがって目的を達するためには(多くの場合)分割された文の末尾に句点を補う必要がある。おそらくこうなるだろう:

text.split(/。/).map{|s| s + "。"}

しかし、もしここで文の条件が変わったらどうか。たとえば、文の区切りとして句点のほかに疑問符「?」が使用可能になったらどうだろう。この場合上記の手法の単純な拡張は失敗する。分割によって失われた記号がいずれだったのかを知ることができないからである。それぞれの記号ごとに分割・補完を繰り返す必要がある。

解法

正規表現の後読みを使えばこの問題は一撃で解決する。具体的には以下のようである。

text.split(/(?<=[。?])/)

ここで記法?<=は「後読み」を表す。後読みというのは、「○○に続く~」を表す記法で、たとえば(?<=A)Bという正規表現は「Aに続くB」にマッチする。「AB」ではなく「(Aに続く)B」であることに注意が必要である。これに従えば上の正規表現は「『。』または『?』に続く(何か)」にマッチすることになる。

さらにこの「(何か)」が指定されていないことにも注意が必要である。この場合この「(何か)」には「長さゼロの文字列」が対応する。「『長さゼロの文字列』にマッチする」とはこの場合文字と文字の間の隙間のことを指している。たとえば以下の式は、すべての文字間にマッチするために文字を一文字ずつに分解する。

text.split(//)

この二つを応用すれば、上の式はこのように理解されるだろう。

「『。』または『?』に続く『文字間』で文字列を分割せよ」

これこそまさに、我々の求めていたものである。