【发布时间】:2017-08-09 16:38:59
【问题描述】:
我会努力在问题上保持客观。
我有一个数据库,其中包含由应用程序创建的数百万个数学公式,这些公式遵循逻辑:
1) 只涉及5种数学运算:加法、减法、乘法、除法和幂(+ - * / ^)
2) 每个操作都由括号分隔/分组。
“参数”可以很简单,例如常量或变量。
示例:(x + 15)
在复合情况下,我可能有:
(x * (x + 15))
请务必记住,使用括号可以保证正确的操作顺序。
例子:
((x * (x + 15)) / 15)
我的挑战是减少这些公式。
当操作相同且无论因素的顺序如何时,它们都没有用或重复很多。
例子:
((((x + 4) + 8) - x) / 12)
等于1,不是公式,是常数,我得忽略它。
并且需要消除像 ((x + 4) + 8) 和 ((8 + x) + 4) 这样的重复。
这就是我需要减少的原因。
请注意,所有公式都使用括号和运算之间的空格进行标准化。
我在 PHP 中创建了一个使用正则表达式进行替换的例程,我成功地将特定公式的可能性从 8000 种减少到不到 300 种。
但是,随着公式的大小(不复杂,因为它们并不复杂)增加,我的例程不再有效。
我需要一个算法,而不是一个例程,来应用我们在学校学到的数学简化。
我认为这是可能的,因为公式是标准化的,并且仅限于 5 种基本数学运算。
我正在使用在 GitHub 中获得的 EvalMath 类来帮助减少和执行公式。
为了让您更好地了解,公式是抽象的,每个“@”都会实时替换为常量和变量。
(@ + @)
(@ / @)
(@ ** @)
((@ + @) + @)
((@ + @) ** @)
((@ - @) + @)
((@ - @) / @)
((@ - @) ** @)
((@ * @) + @)
((@ * @) / @)
((@ * @) ** @)
((@ / @) / @)
这是一个 PHP 代码的 sn-p,它是我的缩减例程的一部分。
在 WHILE (TRUE) 内,我将重复自己的规则分组,直到没有替换。
最后,我有一个数组,其中包含许多重复项,通过对公式元素进行一些减少和重新排序,array_unique() 可以解决这个问题。
我真的需要帮助,我的大脑正被这个问题所困扰。
谢谢。
<?php
$Math_Formulas = array(
'(((x + 7) ** 9) - 9)',
'(((x ^ 3) - 9) - 5)',
'(((2 + x) + x) * x)',
'(((x + 3) / 6) / 8)',
'(((x - 5) + 6) ** 2)',
'(1024 ^ (x / 5))',
'((3 - (x + 6)) + 3)',
'(((x ^ 3) + 9) * 6)',
);
while (TRUE)
{
$changed = FALSE;
// Rule 1: (x - x) = 0
for ($i = 0; $i < count($Math_Formulas); $i++)
{
$Formula = trim(preg_replace_callback('/([^-]?)([a-z]+?) [-] ([a-z]+?)/', function($matches) {
$Expression = $matches[0];
if ($matches[2] == $matches[3])
{
$Expression = $matches[1] . '0';
}
return($Expression);
}, $Math_Formulas[$i]));
if ($Formula != $Math_Formulas[$i])
{
$changed = TRUE;
$Math_Formulas[$i] = $Formula;
}
}
// Rule 2: ...
if (!$changed)
{
break;
}
}
$Math_Formulas = array_values(array_unique($Math_Formulas));
?>
更新 1:
我认为如果在创建公式时使用了“反向波兰符号”,一切都会容易得多,但是使用我拥有的公式,我需要重新排列参数(按字母升序或降序)能够比较它们。
在 RPN 中:
(x + 4) + 5) 变为 "x 4 5 +"
(X + 5) + 4) 变成“x 5 4 +”
如何比较两者?那么更大的函数呢?
我认为我犯了一个错误,我没有详细说明我用于尽可能简化这些公式的 14 个“正则表达式”中使用的技术。过程中不止正则表达式:
原始公式:(((4 - 5) + x) + 8)
第 1 步:二对二常数的加法(或减法或乘法)和表达式的减法,不带括号。
公式:((-1 + x) + 8)
第 2 步:删除 ((n +- n) +- n) 或 (n +- (n +- n)) 的括号。
公式:(-1 + x + 8)
第 3 步:按字母降序重新排列参数。
公式:(x + 8 - 1)
Step 4:在循环中,再次执行Step 1。
最终公式:(x + 7)
还有更多的变换,比如(x + x + x)变成(3 * x),(-x + x)变成0。
所有这些都变得很漂亮,但是当我遇到诸如 ((x * 9) * (x * 5)) / 9) 之类的函数时,这种逻辑就失去了效率。我必须至少再创建 14 条其他嵌套规则。
【问题讨论】:
-
使用 Djikstra 将公式转换为反向波兰表示法,这将有助于消除大量重复;但这不是可以通过正则表达式完成的微不足道的事情
-
你实际上是在问如何用 PHP 创建一个简单的计算机代数系统,这对于堆栈溢出来说太宽泛了。
-
我不清楚您是否真的必须自己编写此代码(并使用 PHP),或者您是否只需要找到解决方案。如果您只需要一个解决方案,我的建议是使用现有的计算机代数系统,例如 Maxima (maxima.sourceforge.net) 或 Sympy (sympy.org) 作为后端。在 Maxima 中,您可以调用
ratsimp来简化表达式; Sympy 中可能有类似的功能。如果您必须编写自己的解决方案,我的建议是查看 P. Norvig 的“AI 编程范式”中概述的小代数系统,并根据您的问题调整这些概念。 -
你好马克,我根据你的建议更新了这个问题。我检查了 Maxima,ratsimp 似乎很有趣,我会做一些测试以节省时间,因为我目前无法沉浸在理论解决方案中,我必须快速给出结果。谢谢罗伯特。我会尽快评估 Sympy。