Match all minuses that are not at the beginning of the string and that do not follow immediately after an `e` or `E`

Viewed 32

And ideally, I want to allow spaces between, say and e and the minus:

(?<!(^|[eE]))\s*-

(the reason \s* is outside the lookbehind is that negative lookbehinds need be a fixed length, which \s* is not)

the logic here makes sense to me: match \s*- unless it is preceded by ^, e or E

this is intended as part of a larger pattern meant to purge e.g. thousands separators from a number string:

[^\d,.\-+eE]|(?<!(^|[eE]))\s*[+\-]|[eE](?!\s*[+\-]?\s*\d+$)|[.,](?=.*[.,])

What this does is (in order), it matches

  • everything that isn't a number, a comma, a dot, a minus, a plus or an E
  • all pluses and minuses that aren't at the beginning of the string and that don't follow e or E
  • all e and E that aren't followed by at least one digit with potentially a plus or minus between the E and the digit
  • all dots and commas except the last dot or comma

i.e. everything matched by this pattern can be replaced with an empty string.

Now let's try that in Java:

private static final Pattern ALL_NON_NUMERICS_EXCEPT_LEADING_MINUS_AND_E_AND_LAST_DOT_OR_COMMA = Pattern.compile("[^\d,.\-+eE]|(?<!(^|[eE]))\s*[+\-]|[eE](?!\s*[+\\-]?\s*\d+$)|[.,](?=.*[.,])");

and

var intermediate = ALL_NON_NUMERICS_EXCEPT_LEADING_MINUS_AND_E_AND_LAST_DOT_OR_COMMA
    .matcher("3 e   -9")
    .replaceAll("");

But as you can see here, the result of that is 3e9 and not 3e-9 as it should.

So I pasted just the (?<!(^|[eE]))\s*- pattern to regex101 and turns out that the lookbehind is "not fixed", after all.

I do think it's possible this results in a mis-compilation of the pattern.

So how do I actually DO this?

1 Answers

First of all, always test your regexps in an environment that is compatible with the one you will be using your regex in. Thus, select "Java", not "PCRE" at regex101.com.

Next, regex101 supports Java 8 regex flavor, and there has been some progress on Java regex support since then, here is a note on lookbehind patterns in Java:

Java 13 allows you to use the star and plus inside lookbehind, as well as curly braces without an upper limit. But Java 13 still uses the laborious method of matching lookbehind introduced with Java 6. Java 13 also does not correctly handle lookbehind with multiple quantifiers if one of them is unbounded. In some situations you may get an error. In other situations you may get incorrect matches. So for both correctness and performance, we recommend you only use quantifiers with a low upper bound in lookbehind with Java 6 through 13.

See the Java demo:

String pattern = "[^\\d,.+eE-]|(?<!(?:^|[eE])\\s*)[+-]|[eE](?!\\s*(?:[+-]\\s*)?\\d+$)|[.,](?=.*[.,])";
Pattern ALL_NON_NUMERICS_EXCEPT_LEADING_MINUS_AND_E_AND_LAST_DOT_OR_COMMA = Pattern.compile(pattern);
var intermediate = ALL_NON_NUMERICS_EXCEPT_LEADING_MINUS_AND_E_AND_LAST_DOT_OR_COMMA
            .matcher("3 e   -9")
            .replaceAll("");
System.out.println(intermediate);
// => 3e-9

Although (?<!(?:^|[eE])\s*) works here, it is still recommended to only use limiting quantifiers in constrained-width lookbehind patterns, i.e. just make sure the upper bound is reasonable enough, e.g. (?<!(?:^|[eE])\s{0,100}).

Related