Определение границ предложения php

Я хотел бы разделить текст на предложения в PHP. В настоящее время я использую регулярное выражение, которое обеспечивает точность ~ 95% и улучшает работу с использованием лучшего подхода. Я видел инструменты NLP, которые делают это в Perl, Java и C, но не вижу ничего, что бы соответствовало PHP. Знаете ли вы о таком инструменте?

Расширенное регулярное выражение

Предполагая, что вы позаботились об обработке: сокращения Mr. и Mrs. и т. Д., То следующее однорежимное решение работает довольно хорошо:

 <?php // test.php Rev:20160820_1800 $split_sentences = '%(?#!php/i split_sentences Rev:20160820_1800) # Split sentences on whitespace between them. # See: http://stackoverflow.com/a/5844564/433790 (?<= # Sentence split location preceded by [.!?] # either an end of sentence punct, | [.!?][\'"] # or end of sentence punct and quote. ) # End positive lookbehind. (?<! # But don\'t split after these: Mr\. # Either "Mr." | Mrs\. # Or "Mrs." | Ms\. # Or "Ms." | Jr\. # Or "Jr." | Dr\. # Or "Dr." | Prof\. # Or "Prof." | Sr\. # Or "Sr." | T\.V\.A\. # Or "TVA" # Or... (you get the idea). ) # End negative lookbehind. \s+ # Split on whitespace between sentences, (?=\S) # (but not at end of string). %xi'; // End $split_sentences. $text = 'This is sentence one. Sentence two! Sentence thr'. 'ee? Sentence "four". Sentence "five"! Sentence "'. 'six"? Sentence "seven." Sentence \'eight!\' Dr. '. 'Jones said: "Mrs. Smith you have a lovely daught'. 'er!" The TVA is a big project! '; // Note ws at end. $sentences = preg_split($split_sentences, $text, -1, PREG_SPLIT_NO_EMPTY); for ($i = 0; $i < count($sentences); ++$i) { printf("Sentence[%d] = [%s]\n", $i + 1, $sentences[$i]); } ?> - <?php // test.php Rev:20160820_1800 $split_sentences = '%(?#!php/i split_sentences Rev:20160820_1800) # Split sentences on whitespace between them. # See: http://stackoverflow.com/a/5844564/433790 (?<= # Sentence split location preceded by [.!?] # either an end of sentence punct, | [.!?][\'"] # or end of sentence punct and quote. ) # End positive lookbehind. (?<! # But don\'t split after these: Mr\. # Either "Mr." | Mrs\. # Or "Mrs." | Ms\. # Or "Ms." | Jr\. # Or "Jr." | Dr\. # Or "Dr." | Prof\. # Or "Prof." | Sr\. # Or "Sr." | T\.V\.A\. # Or "TVA" # Or... (you get the idea). ) # End negative lookbehind. \s+ # Split on whitespace between sentences, (?=\S) # (but not at end of string). %xi'; // End $split_sentences. $text = 'This is sentence one. Sentence two! Sentence thr'. 'ee? Sentence "four". Sentence "five"! Sentence "'. 'six"? Sentence "seven." Sentence \'eight!\' Dr. '. 'Jones said: "Mrs. Smith you have a lovely daught'. 'er!" The TVA is a big project! '; // Note ws at end. $sentences = preg_split($split_sentences, $text, -1, PREG_SPLIT_NO_EMPTY); for ($i = 0; $i < count($sentences); ++$i) { printf("Sentence[%d] = [%s]\n", $i + 1, $sentences[$i]); } ?> 

Обратите внимание, что вы можете легко добавлять или отбирать аббревиатуры из выражения. Учитывая следующий тестовый абзац:

Это предложение одно. Предложение два! Предложение три? Предложение «четыре». Приговор «пять»! Предложение «шесть»? Приговор «семь». Приговор «восемь!» Доктор Джонс сказал: «Миссис Смит, у тебя прекрасная дочь!» TVA – большой проект!

Вот результат скрипта:

Sentence[1] = [This is sentence one.]
Sentence[2] = [Sentence two!]
Sentence[3] = [Sentence three?]
Sentence[4] = [Sentence "four".]
Sentence[5] = [Sentence "five"!]
Sentence[6] = [Sentence "six"?]
Sentence[7] = [Sentence "seven."]
Sentence[8] = [Sentence 'eight!']
Sentence[9] = [Dr. Jones said: "Mrs. Smith you have a lovely daughter!"]
Sentence[10] = [The TVA is a big project!]

Существенное регулярное решение

Автор вопроса прокомментировал, что вышеупомянутое решение «игнорирует многие варианты» и не является достаточно общим. Я не уверен, что это значит, но суть вышеупомянутого выражения примерно такая же чистая и простая, как вы можете получить. Вот:

 $re = '/(?<=[.!?]|[.!?][\'"])\s+(?=\S)/'; $sentences = preg_split($re, $text, -1, PREG_SPLIT_NO_EMPTY); 

Обратите внимание, что оба решения правильно идентифицируют предложения, заканчивающиеся кавычкой после окончания пунктуации. Если вам не /(?<=[.!?])\s+(?=\S)/ соответствующие предложения, заканчивающиеся на кавычки, регулярное выражение может быть упрощено до: /(?<=[.!?])\s+(?=\S)/ .

Edit: 20130820_1000 Добавлен TVA (другое прерывистое слово, которое нужно игнорировать) для регулярного выражения и тестовой строки. (чтобы ответить на вопрос о комментариях PapyRef)

Редактировать: 20130820_1800 Уточнить и переименовать регулярное выражение и добавить shebang. Также исправлены регулярные выражения, чтобы предотвратить разделение текста на конечные пробелы.

Небольшое улучшение на чужой работе:

 $re = '/# Split sentences on whitespace between them. (?<= # Begin positive lookbehind. [.!?] # Either an end of sentence punct, | [.!?][\'"] # or end of sentence punct and quote. ) # End positive lookbehind. (?<! # Begin negative lookbehind. Mr\. # Skip either "Mr." | Mrs\. # or "Mrs.", | Ms\. # or "Ms.", | Jr\. # or "Jr.", | Dr\. # or "Dr.", | Prof\. # or "Prof.", | Sr\. # or "Sr.", | \s[AZ]\. # or initials ex: "George W. Bush", # or... (you get the idea). ) # End negative lookbehind. \s+ # Split on whitespace between sentences. /ix'; - $re = '/# Split sentences on whitespace between them. (?<= # Begin positive lookbehind. [.!?] # Either an end of sentence punct, | [.!?][\'"] # or end of sentence punct and quote. ) # End positive lookbehind. (?<! # Begin negative lookbehind. Mr\. # Skip either "Mr." | Mrs\. # or "Mrs.", | Ms\. # or "Ms.", | Jr\. # or "Jr.", | Dr\. # or "Dr.", | Prof\. # or "Prof.", | Sr\. # or "Sr.", | \s[AZ]\. # or initials ex: "George W. Bush", # or... (you get the idea). ) # End negative lookbehind. \s+ # Split on whitespace between sentences. /ix'; 
 $sentences = preg_split($re, $story, -1, PREG_SPLIT_NO_EMPTY); 

В качестве низкотехнологичного подхода вы можете рассмотреть возможность использования серии explode вызовов в цикле с использованием.,! И? как игла. Это будет очень интенсивно для памяти и процессора (поскольку большинство текстовых обработок). У вас будет куча временных массивов и один главный массив со всеми найденными предложениями, численно проиндексированными в правильном порядке.

Кроме того, вам нужно будет проверить общие исключения (например, в титрах, таких как г-н и д-р ), но со всем, что находится в массиве, эти проверки не должны быть такими уж плохими.

Я не уверен, что это лучше, чем регулярное выражение с точки зрения скорости и масштабирования, но это было бы целесообразно. Насколько велики эти блоки текста, которые вы хотите разбить на предложения?

Я использовал это регулярное выражение:

 preg_split('/(?<=[.?!])\s(?=[AZ"\'])/', $text); 

Не будет работать над предложением, начинающимся с числа, но также должно иметь очень мало ложных срабатываний. Конечно, то, что вы делаете, тоже. Моя программа теперь использует

 explode('.',$text); 

потому что я решил, что скорость важнее точности.

Создайте список сокращений, подобных этому

 $skip_array = array ( 'Jr', 'Mr', 'Mrs', 'Ms', 'Dr', 'Prof', 'Sr' , etc. 

Скомпилируйте их в выражение

 $skip = ''; foreach($skip_array as $abbr) { $skip = $skip . (empty($skip) ? '' : '|') . '\s{1}' . $abbr . '[.!?]'; } 

Запустите этот preg_split, чтобы разбить предложения.

 $lines = preg_split ("/(?<!$skip)(?<=[.?!])\s+(?=[^az])/", $txt, -1, PREG_SPLIT_NO_EMPTY); 

И если вы обрабатываете HTML, следите за удалением тегов, которые устраняют пространство между предложениями. <p></p> Если у вас есть situations.Like where.They этому. where.They склеиваются, становится намного сложнее разобрать.

@ridgerunner Я написал ваш PHP-код в C #

В результате получается 2 предложения:

  • Г-н J. Dujardin régle sa TV
  • A. en esp. uniquement

Правильным результатом должно быть предложение: г-н Дж. Дужардин, рег. Са ТВА, en esp. uniquement

и с нашим тестовым абзацем

 string sText = "This is sentence one. Sentence two! Sentence three? Sentence \"four\". Sentence \"five\"! Sentence \"six\"? Sentence \"seven.\" Sentence 'eight!' Dr. Jones said: \"Mrs. Smith you have a lovely daughter!\" The TVA is a big project!"; 

В результате

 index: 0 sentence: This is sentence one. index: 22 sentence: Sentence two! index: 36 sentence: Sentence three? index: 52 sentence: Sentence "four". index: 69 sentence: Sentence "five"! index: 86 sentence: Sentence "six"? index: 102 sentence: Sentence "seven. index: 118 sentence: " Sentence 'eight!' index: 136 sentence: ' Dr. Jones said: "Mrs. Smith you have a lovely daughter! index: 193 sentence: " The TV index: 203 sentence: A. is a big project! 

Код C #:

  string sText = "Mr. J. Dujardin régle sa TVA en esp. uniquement"; Regex rx = new Regex(@"(\S.+? [.!?] # Either an end of sentence punct, | [.!?]['""] # or end of sentence punct and quote. ) (?<! # Begin negative lookbehind. Mr. # Skip either Mr. | Mrs. # or Mrs., | Ms. # or Ms., | Jr. # or Jr., | Dr. # or Dr., | Prof. # or Prof., | Sr. # or Sr., | \s[AZ]. # or initials ex: George W. Bush, | T\.V\.A\. # or "TVA" ) # End negative lookbehind. (?=|\s+|$)", RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); foreach (Match match in rx.Matches(sText)) { Console.WriteLine("index: {0} sentence: {1}", match.Index, match.Value); }