将文本分割成句子

10

如何将文本分割成句子数组?

示例文本:

煮一只海狸。煮一只海狸!煮一只海狸? 煮第四只海狸吗?煮多只海狸...结束。

输出应为:

0 => Fry me a Beaver.
1 => Fry me a Beaver!
2 => Fry me a Beaver?
3 => Fry me Beaver no. 4?!
4 => Fry me many Beavers...
5 => End

我尝试了一些在SO上通过搜索找到的解决方案,但它们都失败了,特别是在第四句话。

/(?<=[!?.])./

/\.|\?|!/

/((?<=[a-z0-9)][.?!])|(?<=[a-z0-9][.?!]\"))(\s|\r\n)(?=\"?[A-Z])/

/(?<=[.!?]|[.!?][\'"])\s+/    // <- closest one

2
第四句话不符合标准语法。你需要一个终止符类-用于标记句子的结尾。如果你将其中一个终止符号用作普通符号,那么它要么不是终止符,要么你在错误地组成句子。简单来说,你不能两全其美。 - Shark
我经常制作蛋糕并且吃掉它们 :P 一个正则表达式能否向前查看2个字符,如果第二个字符不是大写字母A-Z,则表示前面的标点符号无效。 - thelolcat
听起来你已经知道需要做什么了。 - Shark
但是我该如何将它放入正则表达式中呢? - thelolcat
这个世界上的计算机应该知道这个句子的结尾是“no. 4?!”吗?如果是“no. 4(3之后的数字)?!”呢?你现在正在进入为Chuck Norris保留的领域。 - hek2mgl
显示剩余2条评论
2个回答

31

既然您想要“拆分”句子,为什么还要尝试匹配它们呢?

在这种情况下,让我们使用preg_split()

代码:

$str = 'Fry me a Beaver. Fry me a Beaver! Fry me a Beaver? Fry me Beaver no. 4?! Fry me many Beavers... End';
$sentences = preg_split('/(?<=[.?!])\s+(?=[a-z])/i', $str);
print_r($sentences);

输出:

Array
(
    [0] => Fry me a Beaver.
    [1] => Fry me a Beaver!
    [2] => Fry me a Beaver?
    [3] => Fry me Beaver no. 4?!
    [4] => Fry me many Beavers...
    [5] => End
)

解释:

简单来说,我们通过分组空格\s+进行拆分,并执行两个操作:

  1. (?<=[.?!]) 正向后瞻断言,基本上我们搜索是否在空格后面有句号、问号或感叹号。

  2. (?=[a-z]) 正向前瞻断言,搜索空格后面是否有字母。这是绕过问题no. 4的一种方法。


1
只是一个问题:\s 不应该是 \s+ 吗?我的意思是忽略在一起的多个空格。 - thelolcat
1
@Ryan 快速 (?<!\.\.\.)(?<=[.?!]|\.\))\s+(?=[a-z])。看看它是否符合您的需求。 - HamZa
3
根据我从您那里学到的知识,我能够进行修改以处理更多我遇到的边缘情况:https://regex101.com/r/e4NYyd/4 很棒的东西。 - Ryan
1
这个不行。尝试在句子中加入"i.e.",这个正则表达式会失败。 - Richard
1
同时,它也不能处理以先生(Mr.)和感叹号(!)结尾的句子。 - holden321
显示剩余12条评论

1
我建议您在不使用回顾断言的情况下搜索分隔标点,然后释放这些匹配的字符(使用\K),然后匹配空格,最后向前查找大写字母以表示下一个句子的开始。
代码:(演示)
$str = 'Fry me a Beaver. Fry me a Beaver! Fry me a Beaver? Fry me Beaver no. 4?! Fry me many Beavers... End';

var_export(
    preg_split('~[.?!]+\K\s+(?=[A-Z])~', $str, 0, PREG_SPLIT_NO_EMPTY)
);

输出:

array (
  0 => 'Fry me a Beaver.',
  1 => 'Fry me a Beaver!',
  2 => 'Fry me a Beaver?',
  3 => 'Fry me Beaver no. 4?!',
  4 => 'Fry me many Beavers...',
  5 => 'End',
)

虽然不需要对示例字符串进行处理,但使用PREG_SPLIT_NO_EMPTY可以防止在数组末尾创建空元素,如果字符串以标点符号结尾。

在我的回答中使用 \K 可以减少回溯。这使得正则表达式引擎在扫描字符串时更加高效。在 Hamza 的回答中,正则表达式引擎每次匹配到空格时开始匹配,然后在匹配空格后,需要向后读取来检查标点符号,然后如果符合条件,它还需要向前查找字母。

在我的方法中,当正则表达式引擎遇到列出的标点符号之一时,才开始考虑匹配,并且它永远不会回溯。有许多空格要匹配,但较少的符号符合条件。因此,在示例输入字符串上,我的模式将字符串分成40个步骤,而Hamza 的模式将字符串分成74个步骤

这种效率在相对较小的字符串上并没有太大的优势,但是,如果您正在解析大型文本,则效率和最小化回溯变得更加重要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接