【问题标题】:How to simplify a mathematical formula by reducing it as much as possible如何通过尽可能减少数学公式来简化它
【发布时间】: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。

标签: php math formulas


【解决方案1】:

只允许四个操作(+ - * /),所有这些表达式都是所谓的有理分数,即两个多项式的比率。

确实,以下规则适用:

p/q + p'/q'   = (pq' + p'q)/pq
p/p.p'/'q     = (pp')/(qq')
(p/p')/(q/q') = (pq')/(p'q)

所以两个有理分数的组合也是一个有理分数。

您可以实现一个系统,其中每个表达式都被描述为一对多项式(具有整数或有理系数),并整合上述公式。这实质上需要多项式加法和乘法。

有时,分子和分母会有公因数,需要简化。您可以通过多项式上的欧几里德算法发现这些,给出多项式 GCD。 (那么你也需要多项式除法。)

通过这个系统,简化会自行发生。此外,表达式将被规范化,以便您发现重复项。

如果只有一个变量,多项式将是单变量的,并且它们的表示很简单。对于几个变量,它变得更加复杂。

现在,如果您允许取幂,则需要区分两种情况:

  • 指数只能是正整数:那么你可以通过多项式规则将p^nq^n扩展为其他多项式(但大小会很快爆炸);

  • 指数可以是有理常数或变量的函数:整个方法都崩溃了。

最后一句话:

最后,您必须生成简化的评估规则,即两个多项式的比率。表达这种评估的有效方式是霍纳的方案。在某些情况下,这会让您回到起点!

例子:

简化

(1 - x / y) (x + y) + (x * x) / y

步骤:

1 - x / y                 => (y - x) / y
x + y                     => (x + y) / 1
(y - x) / y . (x + y) / 1 => (y² - x²) / y
x * x                     => x² / 1
y                         => y / 1
(x * x) / y               => x² / y
(y² - x²) / y + x² / y    => y³ / y²
y³ / y²                   => y / 1

【讨论】:

    【解决方案2】:

    所以一般的方案是首先将表达式转换为表达式树格式。这是一种树形结构,其中每个节点代表一个数学运算、一个数字或一个变量。您可以使用 shutting yard 算法进行转换,并且可能会发现 evalmath 可以被修改以生成一棵树,而不是简单地评估它。可能还有其他 Python 库可以做的比 evalmath 更多。

    一旦有了树状结构,操作起来就容易多了。您可以应用您在高中学到的一些数学规则来操作小子树以生成更简单的形式。

    例如,您可能想要扩展括号。表达式 (x+3)*(x+5) 将表示为一棵树

                  *
            +           +
         x     3     x     5 
    

    这棵树可以重写为 x^2 + 8 x + 15

                  +
            ^                +
         x     2        *        15
                     x     8
    

    一般方案可能会扩展所有括号。然后收集类似的术语并简化。

    【讨论】:

    • 太棒了 (en.wikipedia.org/wiki/Shunting-yard_algorithm),谢谢。我确定我会在另一份工作中使用它,但我不需要担心公式将如何执行,这里的关键是最大限度地减少我要使用的公式数量使用它们,因为它们将被彻底重用,并且我拥有的公式越少,我可以走得越远。
    猜你喜欢
    • 2020-04-18
    • 1970-01-01
    • 2019-08-27
    • 2021-07-12
    • 1970-01-01
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多