Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.JS Regex engine fails on large input

The question is a bit complicated, and googling didn't really help. I will try to put in only relevant aspects of it.

I have a large document in approximately the following format:

Sample Input:

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 

I am trying to remove the section of the text according to the below:

  • From either of:
    • ABC
    • DEF
    • GHI
  • To either of(while retaining this word):
    • PQR
    • STU
    • VWX

The words that make up "From" can appear anywhere in a line (Look at GHI). But for removal the entire line needs to be removed. (The entire line containing GHI needs to be removed as in the sample output below)

Sample Output:

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 

The above example actually seemed easy for me until I ran it against very large input files ( 49KB)

What I have tried:

The regular expression I am currently using is (with case insensitive and multiline modifier):

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

Problem

The above regexp works wonderfully on small text files. But fails/crashes the engine on large files. I have tried it against the below:

  • V8 (Node.js) : Hangs
  • Rhino : Hangs
  • Python : Hangs
  • Java : StackoverflowError (Stack trace posted at the end of this question)
  • IonMonkey (Firefox) : WORKS!

Actual Input:

  • My original Input: http://ideone.com/W4sZmB
  • My regular expression (split across multiple lines for clarity):

    ^.*\\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
    

Question:

  • Is my regular expression correct? Can it be optimized further to avoid this problem?
  • In case it is correct, why do other engines hang infinitely? A section of stack trace is below:

Stack Trace:

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: I'm adding several tags to this question since I have tried it on those environments and the experiment failed.

like image 539
UltraInstinct Avatar asked May 16 '13 06:05

UltraInstinct


People also ask

Why is my regex so slow?

The reason the regex is so slow is that the "*" quantifier is greedy by default, and so the first ". *" tries to match the whole string, and after that begins to backtrack character by character. The runtime is exponential in the count of numbers on a line.

How is regex so fast?

Good regular expressions are often longer than bad regular expressions because they make use of specific characters/character classes and have more structure. This causes good regular expressions to run faster as they predict their input more accurately.

What is backtracking regex?

Backtracking occurs when a regular expression pattern contains optional quantifiers or alternation constructs, and the regular expression engine returns to a previous saved state to continue its search for a match.

How does node js handle concurrent tasks?

Multiple clients make multiple requests to the NodeJS server. NodeJS receives these requests and places them into the EventQueue . NodeJS server has an internal component referred to as the EventLoop which is an infinite loop that receives requests and processes them.


1 Answers

The problem is the (.|\s)* because any space character will match both and it will allow it to go down both options. This makes it get exponentially larger.

You can see the issue with this regex in ruby

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

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

which takes forever, while a basically identical one

/ba*b/.match str

matches quickly.

You can fix this by either using just .* or if . doesn't match newlines (.|\n)*

like image 78
user71404 Avatar answered Oct 06 '22 15:10

user71404