日本語のパラグラフをセンテンス単位に分割するのって、もちろんいろんなパターンをプログラムで書いていけばできると思うのだが、シンプルなコードでやろうとすると、どうなるんだろうか。
普通に考えると、
- 「。」で区切る
- 「?」や「!」でも区切る(これらは連続するときもある。まあ句点の連続も媒体によってはあり得るが。。。)
- カギ括弧等(丸括弧や二重カギ括弧なども含む)に挟まれている「。!?」では区切らない
- 改行で区切る
ぐらいのルールで区切れば、だいたいの場合はカバーできそうな気はする。もちろんこれら以外に、
- 文中で改行されていると思われる場合は改行を無視する(素材によってはそういうものは存在しないと前提できることも多い。)
- カギ括弧が入れ子になっている場合の対処
などを考え始めるともっとややこしくなるが。
で、Rの文字列操作で、「カギ括弧で挟まれている場合以外は句点で区切る」というのを正規表現でやろうとしたのだが、正規表現になれていないこともあり、頭がこんがらがってなかなか上手くいかない。
とりあえず、{stringr}のstr_extract_allで、
「カギ括弧開くから最短で閉じるまで or 句点を含まない文字列」の1回以上繰り返し or 句点を含まない文字列
という条件で抽出してみると、1センテンスに複数回セリフが出てくる場合や、セリフのなかで句点が複数回出る場合も含めて、抽出することができた。
以下の最後の一文のように、カギ括弧が入れ子になってると、最短マッチにしてるので崩壊する。
文例:
今日、高速道路を走っていたら後ろからパトカーが迫ってきて、「はいグレーのフリード左に寄せて停まってください。」と言うので、恐る恐る路側帯に停車した。お巡りさんが2人、降りてきた。1人が「今、進路変更するとき合図出てなかったですね。」と言い、もう1人は「ちょっとスピードも出過ぎですね。まぁ、20キロぐらいしかオーバーしてないし、今日のところは合図不履行だけで切っときますけど。」と言う。私は、「すいません、出したつもりだったんですけどね……」と答えた。最近、立て続けに「シートベルト」「一時停止」で違反を取られていて、もううんざりだ。興味本位でお巡りさんに、「そういえば昔、茨城県警に「40キロ以上オーバーしないとスピード違反は切らないよ。」と言われましたね。大阪府警も似たような方針ですか。」と訊いてみたのだが、ノーコメントだった。
> x1 <- "今日、高速道路を……省略……ノーコメントだった。" > x2 <- str_extract_all(x1, "((「.*?」)|([^。]))+|[^。]+") > x3 <- paste(x2[[1]], '。', sep='') # お尻に。を付けなおす > print(x3) [1] "今日、高速道路を走っていたら後ろからパトカーが迫ってきて、「はいグレーのフリード左に寄せて停まってください。」と言うので、恐る恐る路側帯に停車した。" [2] "お巡りさんが2人、降りてきた。" [3] "1人が「今、進路変更するとき合図出てなかったですね。」と言い、もう1人は「ちょっとスピードも出過ぎですね。まぁ、20キロぐらいしかオーバーしてないし、今日のところは合図不履行だけで切っときますけど。」と言う。" [4] "私は、「すいません、出したつもりだったんですけどね……」と答えた。" [5] "最近、立て続けに「シートベルト」「一時停止」で違反を取られていて、もううんざりだ。" [6] "興味本位でお巡りさんに、「そういえば昔、茨城県警に「40キロ以上オーバーしないとスピード違反は切らないよ。」と言われましたね。" [7] "大阪府警も似たような方針ですか。" [8] "」と訊いてみたのだが、ノーコメントだった。"
!や?も入れる場合は、「!?」とその繰り返しにも対応するように区切り文字を[^[。!?]+]*1というふうにしておくといいが、
"((「.*?」)|([^[。!?!?]+]))+|[^[。!?!?]+]+"
その場合は「お尻に句点を付け直す」のは無駄なのでやめる。
入れ子問題以外にもいろいろ落とし穴がありそうだが、単に句点で区切るよりはだいぶマシではある。いま、新聞記事のコーパスを解析していて、新聞はそんなに変な表現が出てこないから、これぐらいシンプルなルールでもまぁまぁ行けそう。係り受け解析器とかも駆使するとさらに正確にできるのかもしれないが、知識が追いついてないのと、今やってるタスクではクオリティよりも高速で処理することのほうが優先なので、ひとまずこれ以上は考えずに進めようかな。
入れ子問題も、要するに一番外側のカッコだけ見て、その中身は何でもいいと判断すれば良いわけなので、ある程度単純なルールで行けそうな気はするのだが、自分のスキルではパッと思いつかない。「一番外側のカッコ」を最長マッチで見てしまうと、
●●「◯◯」●●。●●「◯◯」●●。
という文が一文になってしまう。先読みとか戻り読みとかを使うんかな。カッコの開閉の個数が一致することと、上位階層のクラスをまたいで開閉することはできないというルールを入れる必要があるので、正規表現でやることではないのかもしれないが。
参考記事:
日本語の文章をいい感じに文区切りするライブラリを作った - Qiita
*1:これの内側の[]を()にしてもちゃんと動いたのだが、なんで…?