调车场算法中函数的优先级


10

如维基百科所述,我正在研究调车场算法

与运算符打交道时的算法描述如下:

如果令牌是运算符,则为o1,则:

而在运算符堆栈的顶部有一个运算符令牌o2,或者

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

然后将o2从运算符堆栈中弹出,进入输出队列;

将o1推入操作员堆栈。

但是,他们给出了以下示例:

输入: sin max 2 3 / 3 * 3.1415

当算法命中/令牌时,对应该发生的情况的描述如下:

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

它们如雨后春笋般冒出的功能标记max关闭stack,并投入了queue。根据他们的算法,这似乎意味着功能令牌既是运算符,又比/运算符的优先级低。

没有关于这种情况的解释。那么,对于Shunting-yard算法而言,函数的优先级是什么?函数是右还是左关联?还是维基百科不完整/不准确?

Answers:


5

我相信直接的答案仅仅是功能不是运算符。在您链接的页面上:

如果令牌是功能令牌,则将其压入堆栈。

这就是全部,因为功能用例(后缀的前缀)比运算符用例(后缀的前缀)简单得多。

对于后续问题:仅由于在具有多个中缀运算符的任何表达式中的继承歧义性,才需要优先级和关联性的概念。函数令牌已经在使用前缀表示法,因此它们根本没有问题。你不需要知道是否sinmax具有“高优先级”弄清楚,max需要先计算; 从令牌的顺序已经很清楚了。这就是为什么计算机开始时喜欢前/后缀表示法的原因,也是为什么我们拥有这种将中缀转换为前/后缀的算法的原因。

您确实需要某种规则来确定函数的参数在没有括号的情况下开始和结束的位置,因此您可以说函数“优先于”运算符,反之亦然。但是与infix运算符不同,所有功能的一个一致规则足以使它们的构成完全明确。


那么他们的算法是正确的;这是他们的例子,这是不正确的。前缀符号应包括括起功能的括号:sin( max( 2 3) / 3 * 3.1415)
MirroredFate

我不确定是否将其称为不正确,但这是一个强有力的论点,支持需要在所有函数调用前后加上括号和逗号的语言。
Ixrec 2015年

我认为这是不正确的,因为无法使用他们描述的算法来解析中缀。
MirroredFate

@Ixrec我看不到“如果令牌是函数令牌,则将其压入堆栈”行。在Wikipedia页面上。现在可能已经对其进行了编辑。但是,您是说我可以在算法中将函数视为数字吗?
Abhinav '18

我相信,维基百科文章中的算法描述存在错误(到目前为止)。在该短语之后if there is a left parenthesis at the top of the operator stack, then: pop the operator from the operator stack and discard it,应添加另一行:如果现在在堆栈顶部有一个函数名,则将其从堆栈中弹出并压入输出。“:换句话说,左“(”应始终与函数名称一起作为函数的语法意味着弹出,如果它们放在一起name(。”
斯坦

3

根据您的语言语法,有两种情况需要考虑。如果您的语言使用圆括号表示功能应用程序(例如f(2+1)),则优先级无关紧要。该函数应推入堆栈,然后弹出(对于上面的示例,结果为2 1 + f)。或者,您可以将函数视为值并立即将其输出,然后在右括号之后输出函数调用操作(否则应将其与任何其他括号相同),例如f 2 1 + $$函数调用操作在哪里。

但是,如果您的语言不使用括号来表示函数调用,而是将参数直接放在函数之后而没有任何特殊标点符号(例如f 2 + 1),如Wikipedia的示例所示,则情况会稍微复杂一些。请注意,我刚才给一个例子的表达式是模棱两可的:将f应用于结果2和1,还是将2和1相加,然后对结果调用f

同样,有两种方法。您可以在遇到函数时将其简单地推入运算符堆栈,并为其分配所需的优先级。这是最简单的方法,并且显然是引用的示例所做的事情。但是,存在一些实际问题。首先,您如何识别功能?如果您有一个有限的集,这很容易,但是如果您有用户定义的函数,这意味着您的解析器也需要反馈到您的环境中,这会很快变得混乱。以及如何处理带有多个参数的函数?

我的感觉是,对于这种样式的语法,将函数用作函数应用程序运算符更方便使用的值更具意义。然后,只要读取值就可以注入应用程序运算符,而最后读取的内容也是值,因此您不需要任何特殊的方式就能知道哪些标识符是函数。您还可以使用返回函数的表达式(使用函数操作样式很难或不可能)。这意味着您可以使用currying处理多个参数函数,这与尝试直接处理它们相比大大简化了。

然后,您唯一需要确定的是函数应用程序的优先级。选择权取决于您,但是在我使用过的每种语言中,都可以像这样工作,它一直是该语言中最强大的绑定运算符,并且是正确的关联。(唯一有趣的变体是Haskell,除了描述了强绑定版本外,它还具有它的同义词,该符号$是该语言中最弱的绑定运算符,允许f 2 + 1将f应用于2并f $ 2 + 1应用整个表达式的其余部分)


3

在阅读了Dijkstra的原始想法(Algol 60编译器文件的第7-11页,https://ir.cwi.nl/pub/9251)之后,我实现了要求的“调车场功​​能” ,并且需要一个可靠的解决方案,我执行以下操作:

解析:

  • 推送功能描述符
  • 像他的子表达式括号的开头一样,推入一个args左括号“ [”。
  • 从输入中读取一个“(”至“)”平衡参数列表序列
  • 将其推送到输出令牌流
  • 推入args末尾的右括号“]”,就像他的“补偿性右括号”

中缀到后缀(调车场):

  • 添加另一个堆栈,即函数堆栈,就像运算符堆栈一样
  • 扫描功能名称时,将功能信息推送到功能堆栈
  • 看到参数结尾右括号时,弹出函数堆栈以输出

在健壮的测试和复杂的场景中完美工作。在我的应用程序(一个包含命令行参数的表达式扩展器)中,我支持多参数函数和一个逗号“,”令牌来分隔它们,并且它们贯穿整个过程。

示例看起来像“ sqrt(3 ^ 2 + 4 ^ 2)”,变成“ 3 2 ^ 4 2 ^ + sqrt”,最终变成程序认为的参数“ 5”。它是bignum,因此“” binomial(64,32)/ gcd(binomial(64,32),binomial(63,31))“ ==>大事物==>” 2“很有帮助。” 123456 ^ 789“是40,173位数字,并且时间在我的MacBookPro上显示“评估= 0.000390秒”,是如此之快。

我也用它来扩展表中的数据,并找到它的方便。无论如何,这是我在Dijkstra调车场环境中谨慎处理函数调用,多个参数和深层嵌套的技巧。今天是通过独立思考做到的。不知道是否有更好的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.