需要帮助了解递归前缀评估器

Posted

技术标签:

【中文标题】需要帮助了解递归前缀评估器【英文标题】:Need Help Understanding Recursive Prefix Evaluator 【发布时间】:2014-03-14 20:44:33 【问题描述】:

这是我在教科书中找到的一段代码,用于使用递归来评估前缀表达式。我无法理解这段代码及其经历的过程。

    char *a; int i;
    int eval()
       int x = 0;
        while (a[i] == ' ') i++;
        if (a[i] == '+')
           i++; return eval() + eval(); 
        if (a[i] == '*')
           i++; return eval() * eval(); 
        while ((a[i] >= '0') && (a[i] <= '9'))
           x = 10*x + (a[i++] - '0');
        return x;
      

我想我主要对 return 语句以及它最终如何导致解决前缀表达式感到困惑。提前致谢!

【问题讨论】:

一个建议(因为我刚刚偶然发现了 Eric Lippert 的一篇文章)是将技术细节与逻辑分开。我在这里解释埃里克。在您的代码中,重要的东西似乎是索引和字符。但是您实际上在做(并试图掌握)是建立抽象对象(即数字和运算符)之间关系的模型,并对其进行评估。 (顺便说一句,模型恰好建立在堆栈上。;-) 。)所以我首先将文本转换为逻辑项,即标记(即 lex 它),然后以抽象方式处理这些。分而治之。 【参考方案1】:

理解递归示例的最好方法是通过一个示例:

char* a = "+11 4"

首先,i 被初始化为 0,因为没有默认初始化程序。 i 也是全局的,因此对其的更新将影响所有对 eval() 的调用。

i = 0, a[i] = '+'

没有前导空格,所以第一个 while 循环条件失败。第一个 if 语句成功,i 递增到 1 并执行 eval() + eval()。我们将一次评估这些,然后在得到结果后再回来。

i = 1, a[1] = '1'

同样,没有前导空格,所以第一个 while 循环失败。第一个和第二个 if 语句失败。在最后一个 while 循环中,'1' 介于 0 和 9 之间(基于 ascii 值),因此 x 变为 0 + a[1] - '0',或 0 + 1 = 1。这里重要的是 @987654330 @ 在读取 a[i] 后递增,然后 i 递增。 while 循环的下一次迭代添加到 x。这里 x = 10 * 1 + a[2] - '0',或 10 + 1 = 11。使用正确的 x 值,我们可以退出 eval() 并返回第一个操作数的结果,这里还是 11 .

i = 2, a[2] = '4'

和上一步一样,在这个 eval() 调用中执行的唯一语句是最后一个 while 循环。 x = 0 + a[2] - '0',或 0 + 4 = 4。所以我们返回 4。

此时,控制流返回到对 eval() 的原始调用,现在我们有了两个操作数的值。我们简单地执行加法得到 11 + 4 = 15,然后返回结果。

【讨论】:

感谢您的回复!它肯定有很大帮助。我明白了很多,但我仍然对“返回 eval() + eval()”这段代码如何知道在哪里停止感到困惑。它怎么知道在你的例子中让最后一个数字“4”,在它自己的评估中?这是我最困惑的事情,但我觉得这对此至关重要。 "return eval()+eval()" 代码中的第一个 eval() 将开始一个 '1' 并在空间上结束,已捕获 11。第二个 eval() 调用将剥离空格,然后捕获 4,在返回 4 的字符串的空终止符处停止。 这种行为很危险,在eval() + eval() 表达式中没有指定调用函数的顺序。因为它只使用+*(交换操作),这没关系;但是添加-/,所有的地狱都会崩溃......【参考方案2】:

每次调用 eval() 时,它都会计算从位置 i 开始的下一个表达式的值,并返回该值。

在 eval 内: 第一个 while 循环只是忽略所有空格。 那么有3种情况:

(a) 计算以 + 开头的表达式(即 A+B 形式的表达式,前缀为 "+ A B"

(b) 计算以 * 开头的表达式(即 A*B = "* A B"

(c) 计算整数值(即任何连续的数字序列)

最后的while循环处理情况(c)。

案例 (a) 的代码与案例 (b) 的代码相似。想想案例(一): 如果我们遇到 + 号,这意味着我们需要添加我们在序列中找到的接下来的两个“things”。 “things”可能是数字,也可能是要计算的表达式(例如 X+Y 或 X*Y)。

为了得到这些“事物”是什么,函数 eval() 被调用,并使用更新后的 i 值。每次调用 eval() 都会获取下一个表达式的值,并更新位置 i。

因此,对 eval() 的 2 次连续调用获得了以下 2 个表达式的值。 然后我们将 + 运算符应用于这 2 个值,并返回结果。

通过 "+ * 2 3 * 4 5" 之类的示例会有所帮助,它是 (2*3)+(4*5)。

【讨论】:

感谢您的回复!这对我来说肯定变得更加清楚了。如果我也想在递归中做一个后缀评估器,我只需要倒退吗?那么我是否从字符串的长度开始,然后执行“i--”?或者那行不通【参考方案3】:

所以这段代码只能吃+、*、空格和数字。它应该吃一个可以是以下命令之一的命令:

- + <op1> <op2>
- * <op1> <op2>
<number>

它获取一个指向字符串的指针,以及一个随着程序沿着该字符串前进而递增的读取位置。

   char *a; int i;
    int eval()
       int x = 0;
        while (a[i] == ' ') i++; // it eats all spaces
        if (a[i] == '+') 
       /* if the program encounters '+', two operands are expected next. 
          The reading position i already points just before the place 
          from which you have to start reading the next operand 
          (which is what first eval() call will do). 
          After the first eval() is finished, 
          the reading position is moved to the begin of the second operand, 
          which will be read during the second eval() call. */
           i++; return eval() + eval(); 
        if (a[i] == '*') // exactly the same, but for '*' operation.
           i++; return eval() * eval(); 
        while ((a[i] >= '0') && (a[i] <= '9')) // here it eats all digit until something else is encountered.
           x = 10*x + (a[i++] - '0'); // every time the new digit is read, it multiplies the previously obtained number by 10 and adds the new digit.
        return x; 
        // base case: returning the number. Note that the reading position already moved past it. 
      

【讨论】:

【参考方案4】:

您给出的示例使用了几个全局变量。它们在函数范围之外持续存在,并且必须在调用函数之前进行初始化。 i 应该初始化为 0 以便从字符串的开头开始,并且前缀表达式是 a 中的字符串。

操作符是你的前缀,所以应该是你的第一个非空白字符,如果你以一个数字(数字字符串)开头,你就完成了,这就是结果。

示例:a = " + 15 450"

eval() finds '+' at i = 1
    calls eval() 
        which finds '1' at i = 3 and then '5' 
        calculates x = 1 x 10 + 5 
    returns 15
    calls eval() 
        which finds '4' at i = 6 and then '5' and then '0'
        calclulates x = ((4 x 10) + 5) x 10) + 0
    returns 450
    calculates the '+' operator of 15 and 450
returns 465

返回值要么是找到的值,要么是运算符的结果和找到的后续结果。 因此,函数递归地依次查看输入字符串并执行操作,直到字符串结束或发现无效字符。

【讨论】:

【参考方案5】:

与其将代码分解成块等,我会尽量简单地解释这个概念。

eval 函数总是跳过空格,以便它指向当前位置的数字字符 ('0'->'9')、加法 ('+') 或乘法 ('*')表达式字符串。

如果它遇到一个数字,它会继续吃数字数字,直到它到达一个非数字数字,以整数格式返回总结果。

如果遇到运算符('+' 和 '*'),它需要两个整数,因此 eval 会调用自己两次以从表达式字符串中获取接下来的两个数字并将结果作为整数返回。

【讨论】:

【参考方案6】:

汤中的一根头发可能是评估顺序,参见。 https://www.securecoding.cert.org/confluence/display/seccode/EXP10-C.+Do+not+depend+on+the+order+of+evaluation+of+subexpressions+or+the+order+in+which+side+effects+take+place。

没有指定“eval() + eval()”中的哪个 eval 是首先计算的。这对于交换运算符来说是可以的,但对于 - 或 / 会失败,因为 eval() 作为副作用会推进全局位置计数器,以便(及时)第二个 eval 获得(在空间中)第二个表达式。但这很可能是(在太空中)第一次评估。

我认为修复很容易;分配给一个 temp 并用它计算:

if (a[i] == '-')
       i++; int tmp = eval(); return tmp - eval(); 

【讨论】:

以上是关于需要帮助了解递归前缀评估器的主要内容,如果未能解决你的问题,请参考以下文章

前缀(抛光)表示法 - 评估 c++

TensorFlow MNIST 初学者需要一些了解评估步骤

IP寻址 - 网络前缀 - 帮助理解

C ++业务规则表达式解析器/评估[关闭]

上下文切换,你确定了解吗?

为啥将迭代器作为参数传递并在尾部位置递归时后缀失败并且前缀工作正常? [复制]