【问题标题】:Regular Expression to Validate a Math Expression验证数学表达式的正则表达式
【发布时间】:2016-02-22 17:46:33
【问题描述】:

我正在尝试确定给定的输入是否是有效的数学表达式。这是我提出的当前代码,但只有当 Input 是单个整数(例如 100、200、5、7)时才会返回 true。

Pattern pattern = Pattern.compile("-?\\w+|[-+*%/()]");
Matcher match = pattern.matcher(Input);

if(pattern.matcher(Input).matches())
{
    System.out.print("True");
}
else
    System.out.print("False");

关于我要完成的工作的更多信息:

为简单起见,假设 只有整数(因此没有变量和小数位)。
运算符有:+、-、*、/、%。
仅括号(因此没有括号或大括号)。

例子:

有效:

123  
1*2(3+4)%7  
3--4+5*-7  
13(12)+11-(7*15%(11-2)/4)  
(((((-99999)))))

无效

1+2)  
)5--  
3+*12  
)(++**//
(50)+12)

另外,如果可能的话,是否也可以包含一个关于正则表达式如何工作的简单解释?我对这个话题很陌生。我从概念上理解它,但在我的代码中实现它时遇到了麻烦。

【问题讨论】:

  • (Java) 正则表达式不能做到这一点。一些正则表达式引擎确实支持此类任务所需的递归等功能,但 Java 不在其中。
  • 我认为正则语言在这里有它的局限性。为此,请尝试查找上下文无关语法(如果 Java 支持)。
  • 是这样吗?我的(错误)印象是,如果可以构造 CFG,那么也可以创建可实现的正则表达式。
  • 您的主要问题是括号/表达式的无限嵌套:Java 中的正则表达式不适合递归,因此您的问题没有答案。你需要一个解析器。
  • 如果您放弃搜索正则表达式解决方案,请开始阅读this question 的答案。我想你会发现一些有用的东西。例如 Javascript 引擎的求值器。如果它抛出ScriptException,则表达式无效。

标签: java regex validation math expression


【解决方案1】:

正如几位 cmets 所说,只是正则表达式匹配是不可能的。事实上,匹配平衡括号是经典的“简单正则表达式无法解决的问题”之一。只要您的数学表达式可以包含任意嵌套的括号,您就无法使用正则表达式对其进行验证。

但是,可以验证较小的语言,然后我们可以通过一些编码将其构建到您的语言的验证例程中。较小的语言与您的语言一样,但有一个变化:不允许使用括号。然后,该语言中的有效表达式如下所示:

INTEGER OP INTEGER OP INTEGER OP .... OP INTEGER

另一种说法是“一个INTEGER 后跟零个或多个OP INTEGER 序列”。这可以翻译成正则表达式,如:

Pattern simpleLang = Pattern.compile("-?\\d+([-+*%/]-?\\d+)*");

所以-?\d+ 表示INTEGER[-+*%/] 表示OP。好的,现在我们如何使用它?好吧,首先让我们修改它以在整数之间添加任意空格,并将模式设为static,因为我们将把这个验证逻辑包装在一个类中:

static Pattern simpleLang = Pattern.compile("\\s*-?\\d+(\\s*[-+*%/]\\s*-?\\d+)*\\s*");

(但请注意,我们不允许在负号和后面的数字之间有空格,因此不允许使用 3 - - 4,即使允许使用 3 - -4

现在,要验证完整的语言,我们需要做的是反复找到一个位于最内圆括号级别的块(因此,一个块本身不包含括号,但被一个开闭括号包围),验证括号内的东西匹配简单的语言,然后用一些整数替换那个块(包括周围的括号),用空格包围,这样它就被认为与周围的东西分开了。所以逻辑是这样的:

  • expr进来的是11 - (7 * 15 % (11 - 2) / 4)
  • 最里面的括号块是11 - 2
  • 11 - 2 是否匹配简单语言?是的!
  • 用某个整数替换(11 - 2)。例如,1
  • expr 现在是 11 - (7 * 15 % 1 / 4)
  • 最里面的括号块是7 * 15 % 1 / 4
  • 7 * 15 % 1 / 4 是否匹配简单语言?是的!
  • 用某个整数替换(7 * 15 % 1 / 4)。例如,1
  • expr 现在是 11 - 1
  • 没有括号,所以问:expr 匹配简单的语言吗?是的!

在代码中,这可以解决:

static Pattern simpleLang = Pattern.compile("\\s*-?\\d+(\\s*[-+*%/]\\s*-?\\d+)*\\s*");
static Pattern innerParen = Pattern.compile("[(]([^()]*)[)]");
public static boolean validateExpr(String expr) {
    while (expr.contains(")") || expr.contains("(")) {
        Matcher m = innerParen.matcher(expr);
        if (m.find()) {
            if (!simpleLang.matcher(m.group(1)).matches()) {
                return false;
            }
            expr = expr.substring(0,m.start()) + " 1 " + expr.substring(m.end());
        } else {
            // we have parens but not an innermost paren-free region
            // This implies mismatched parens
            return false;
        }
    }
    return simpleLang.matcher(expr).matches();
}

请注意,有一个您称为“有效”的表达式不会被称为有效:即表达式13(12)+11-(7*15%(11-2)/4)。这将被视为无效,因为在 13 和 12 之间没有运算符。如果您希望允许这种隐式乘法,最简单的方法是添加 (空格字符)作为允许的运算符语言,所以把simpleLang改成:

static Pattern simpleLang = Pattern.compile("\\s*-?\\d+(\\s*[-+ *%/]\\s*-?\\d+)*\\s*");

【讨论】:

  • 是的,可能有更有效的方法来验证这一点(除此之外,我可以在Matcher 上使用replacement 方法),但我的目标是“最容易理解”而不是效率。
  • 它不能完美地与有效表达式 ((a+b)+(c*d)) 一起工作,目前它被认为是错误的表达式
  • 当然。问题非常明确地指出:“为简单起见,假设 仅整数(因此没有变量和小数位)”。如果您希望将其扩展为包含变量,则需要更改 simpleLang 以包含除整数之外的变量。