Node.JS Двигатель Regex выходит из строя на большом входе

Вопрос немного сложный, и googling действительно не помог. Я постараюсь включить в него только соответствующие аспекты.

У меня есть большой документ примерно в следующем формате:

Пример ввода:

ABC is a word from one line of this document. It is followed by
some random line
PQR which happens to be another word.
This is just another line
I have to fix my regular expression.
Here GHI appears in the middle.
This may be yet another line.
VWX is a line
this is the last line 

Я пытаюсь удалить раздел текста в соответствии с ниже:

  • От любого из:
    • ABC
    • DEF
    • ГХИ
  • К любому из (сохраняя это слово):
    • PQR
    • STU
    • VWX

Слова, которые составляют "От", могут появляться в любом месте строки (смотрите GHI). Но для удаления вся строка должна быть удалена. (Вся строка, содержащая GHI, должна быть удалена, как показано на выходном примере ниже)

Результат вывода:

PQR which happens to be another word.
This is just another line
I have to fix my regular expression.
VWX is a line
this is the last line 

Приведенный выше пример действительно казался мне легким, пока я не запустил его против очень больших входных файлов (49 КБ)

Что я пробовал:

Регулярное выражение, которое я использую в настоящее время (с нечувствительным к регистру и многострочным модификатором):

^.*\b(abc|def|ghi)\b(.|\s)*?\b(pqr|stu|vwx)\b

Проблема

Вышеописанное regexp прекрасно работает с небольшими текстовыми файлами. Но сбой/сбой двигателя на больших файлах. Я пробовал его против ниже:

  • V8 (Node.js): Hangs
  • Rhino: Hangs
  • Python: Hangs
  • Java: StackoverflowError (трассировка стека, отправленная в конце этого вопроса)
  • IonMonkey (Firefox): РАБОТЫ!

Фактический ввод:

  • Мой исходный вход: http://ideone.com/W4sZmB
  • Мое регулярное выражение (для ясности разделено на несколько строк):

    ^.*\\b(patient demographics|electronically signed|md|rn|mspt|crnp|rt)\\b
     (.|\\s)*?
     \\b(history of present illness|hpi|chief complaint|cc|reason for consult|patientis|inpatient is|inpatientpatient|pt is|pts are|start end frequency user)\\b
    

Вопрос:

  • Правильно ли это выражение? Можно ли его оптимизировать, чтобы избежать этой проблемы?
  • Если это правильно, почему другие двигатели бесконечно зависают? Ниже приведен фрагмент трассировки стека:

Трассировка стека:

Exception in thread "main" java.lang.StackOverflowError
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4218)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4078)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3345)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4114)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4168)
    at java.util.regex.Pattern$LazyLoop.match(Pattern.java:4357)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4227)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4078)

PS: Я добавляю несколько тегов к этому вопросу, так как я пробовал его в этих средах, и эксперимент не удался.

Ответ 1

Проблема заключается в (. |\s) *, потому что любой символ пробела будет соответствовать обоим, и это позволит ему спуститься по обеим опциям. Это делает его экспоненциально большим.

Вы можете увидеть проблему с этим регулярным выражением в ruby ​​

str = "b" + "a" * 200 + "cbab"

/b(a|a)*b/.match str

который берет навсегда, в то время как в основном идентичный

/ba*b/.match str

выполняется быстро.

Вы можете исправить это, используя только .* или если . не соответствует символам новой строки (.|\n)*

Ответ 2

У меня возникнет соблазн попробовать упростить re. Сейчас не очень сложно быть честным, но как насчет:

\b(abc|def|ghi)\b.*\b(pqr|stu|vwx)\b

Разве это еще не все, что вам нужно, но с началом привязки строки и ненужным необязательным элементом в середине? Не имеет значения, но может быть стоит попробовать.

Ответ 3

Я думаю, что ваша проблема может заключаться в том, что по мере того, как файлы становятся длиннее и длиннее, вы можете сопоставлять пары от и до блоков, идущих примерно на nxm/2. Это означает, что вы получаете экспоненциально больше результатов, которые занимают все больше и больше исходного файла. Если файл начинался с ABC и заканчивался VWX, то одним из совпадений был бы весь файл.

Чтобы придать регулярному выражению меньше совпадений, мой первый подход состоял бы только в регулярном выражении на (abc|def|ghi) и (pqr|stu|vwx) отдельно. После того, как вы вернете результаты, вы можете пройти через каждый из совпадений и попытаться найти первое совпадение для блокировки. Некоторый псевдо-код для выполнения этого будет

from = regex.match(file, '(abc|def|ghi)')
to = regex.match(file, '(pqr|stu|vwx)')
for each match in from:
  for index in to:
    if index > match:
      add index, match to results
      break
for each result:
  parse backwards to the beginning of the line
  edit the file to remove the matching text

Хотя это создает больше работы для себя, это означает, что парсеру регулярных выражений не нужно хранить весь n kB файл в памяти сразу и более эффективно анализировать через маленькие блоки.