为什么后缀(rpn)表示法比前缀更常用?
Why postfix (rpn) notation is more used than prefix?
我所说的使用是指它在许多计算器中的使用,例如 HP35-
我的猜测(和困惑)是 -
- postfix 实际上是更有效的内存 -(SO post 评论 here)。 (混淆 - 两者的评估算法与堆栈相似)
- 当时计算器中的键盘输入类型(混淆 - 这应该无关紧要,因为它只取决于首先或最后给出的运算符的顺序)
可以问这个问题的另一种方式是 postfix notation 相对于 prefix 有什么优势?
谁能赐教一下?
一方面,更容易实施评估。
有了前缀,如果你推送一个操作符,然后是它的操作数,你需要知道操作符何时拥有它的所有操作数。基本上,您需要跟踪您推送的运算符何时拥有所有操作数,以便您可以展开堆栈并进行评估。
由于一个复杂的表达式最终可能会在堆栈上有许多运算符,因此您需要一个可以处理此问题的数据结构。
例如,这个表达式:- + 10 20 + 30 40
将同时在堆栈上有一个 -
和一个 +
,对于每个,你需要知道你是否有可用的操作数。
带后缀,当你压入一个运算符时,操作数(应该)已经在堆栈上,只需弹出操作数并计算。你只需要一个可以处理操作数的栈,不需要其他数据结构。
基本上,因为如果您在 postfix 中编写表达式,您可以仅使用 Stack:
读取表达式的下一个元素,
如果是操作数,则入栈,
否则从Operation需要的Stack中读取操作数,并将结果压入Stack。
如果不是表达式结束,转到1。
例子
expression = 1 2 + 3 4 + *
stack = [ ]
Read 1, 1 is Operand, Push 1
[ 1 ]
Read 2, 2 is Operand, Push 2
[ 1 2 ]
Read +, + is Operation, Pop two Operands 1 2
Evaluate 1 + 2 = 3, Push 3
[ 3 ]
Read 3, 3 is Operand, Push 3
[ 3 3 ]
Read 4, 4 is Operand, Push 4
[ 3 3 4 ]
Read +, + is Operation, Pop two Operands 3 4
Evaluate 3 + 4 = 7, Push 7
[ 3 7 ]
Read *, * is Operation, Pop two Operands 3 7
Evaluate 3 * 7 = 21, Push 21
[ 21 ]
如果您希望您的人类阅读顺序与机器基于堆栈的评估顺序相匹配,那么 postfix 是一个不错的选择。
也就是说,假设您是从左到右阅读的,并不是每个人都这样做(例如希伯来语、阿拉伯语……)。并假设您的机器使用堆栈进行评估,但并非所有人都这样做(例如术语重写 - 参见 Joy)。
另一方面,当机器评估 "back to front/bottom-to-top" 时,人类偏好的前缀没有任何问题。如果关注的是评估 as 令牌到达,序列化也可以逆转。工具帮助在前缀表示法中可能会更好(首先了解 functions/words 可能有助于确定有效参数的范围),但您始终可以从右到左 type。
我认为这只是一个约定...
前缀表示法可能更常用...在数学中,在 F(x,y) 等表达式中。这是一个非常古老的约定,但与许多旧系统(英尺和英寸、信纸)一样,与我们使用更精心设计的系统所能做的相比,它有缺点。
几乎每一年的大学数学教科书都至少要浪费一页来解释f(g(x))
意味着我们先申请g
然后f
。按阅读顺序这样做更有意义:x.f.g
意味着我们首先应用 f
。那么如果我们想申请 h
"after" 我们就说 x.f.g.h
.
例如,考虑我最近不得不处理的 3d 旋转问题。我们想根据 XYZ 约定旋转矢量。在 postfix 中,操作是 vec.rotx(phi).roty(theta).rotz(psi)
。使用前缀,我们必须重载 *
或 ()
然后反转操作顺序,例如 rotz*roty*rotx*vec
。当您想考虑更大的问题时,这很容易出错并且不得不一直考虑它。
例如,我在别人的代码中看到类似rotx*roty*rotz*vec
的东西,我不知道这是一个错误还是不寻常的ZYX轮换约定。我还是不知道。该程序有效,因此它在内部是自洽的,但在这种情况下,前缀表示法使其难以维护。
前缀表示法的另一个问题是,当我们(或计算机)解析表达式 f(g(h(x)))
时,我们必须将 f
保存在内存中(或堆栈中),然后 g
,然后 h
,然后 ok 我们可以将 h
应用于 x
,然后我们可以将 g
应用于结果,然后 f
应用于结果。与 x.f.g.h
相比,内存中的东西太多了。在某些时候(对于人类来说比计算机要快得多)我们会 运行 内存不足。那样的失败并不常见,但为什么在 x.f.g.h
不需要短期记忆的情况下还要打开那扇门。就好比递归和循环的区别
还有一件事:f(g(h(x)))
有很多括号,它开始看起来像 Lisp。就运算符优先级而言,后缀表示法是明确的。
一些数学家(尤其是 Nathan Jacobson)曾尝试改变约定,因为后缀在顺序非常重要的非交换代数中更容易使用,但收效甚微。但是既然我们有机会在计算方面做得更好,我们应该抓住这个机会。
两种符号的离线评估在理论机器中是相同的
(Eager evaluation strategy)仅用一个堆栈进行评估(不将运算符放入堆栈)
可以通过评估前缀符号 right-to-left
.
来完成
- 7 + 2 3
# evaluate + 2 3
- 7 5
# evaluate - 7 5
2
与计算后缀符号相同 left-to-right
.
7 2 3 + -
# put 7 on stack
7 2 3 + -
# evaluate 2 3 +
7 5 -
# evaluate 7 5 -
2
(优化的短路策略)使用两个堆栈(一个用于运算符,一个用于操作数)进行评估
可以通过评估前缀符号 left-to-right
.
来完成
|| 1 < 2 3
# put || in instruction stack, 1 in operand stack or keep the pair in stack
instruction-stack: or
operand-stack: 1
< 2 3
# push < 2 3 in stack
instruction-stack: or, less_than
operand-stack: 1, 2, 3
# evaluate < 2 3 as 1
instruction-stack: or
operand-stack: 1, 1
# evaluate || 1 1 as 1
operand-stack:1
请注意,我们可以在这里轻松地对 boolean
表达式进行 短路优化(与之前的评估序列相比)。
|| 1 < 2 3
# put || in instruction stack, 1 in operand stack or keep the pair in stack
instruction-stack: or
operand-stack: 1
< 2 3
# Is it possible to evaluate `|| 1` without evaluating the rest ? Yes !!
# skip < 2 3 and put place-holder 0
instruction-stack: or
operand-stack: 1 0
# evaluate || 1 0 as 1
operand-stack: 1
与计算后缀符号相同 right-to-left
.
(优化的短路策略)用一个带元组的堆栈进行评估(同上)
可以通过评估前缀符号 left-to-right
.
来完成
|| 1 < 2 3
# put || 1 in tuple-stack
stack tuple[or,1,unknown]
< 2 3
# We do not need to compute < 2 3
stack tuple[or,1,unknown]
# evaluate || 1 unknown as 1
1
与计算后缀符号相同 right-to-left
.
人工从左到右输入数据时在计算器中进行在线评估
在计算器中输入数字时,后缀记法 2 3 +
可以立即求值,而人类将要输入的符号一无所知。它与前缀表示法相反,因为当我们有 - 7 +
时我们无事可做,直到我们得到像 - 7 + 2 3
.
这样的东西
人工从右到左输入数据时在计算器中进行在线评估
现在 Prefix-notation 可以立即评估 + 2 3
,而 Postfix-notation 等待进一步输入时有 3 + -
.
请参考@AshleyF,注意阿拉伯语是从右到左书写的,而英语是从左到写的!
我猜 little-endian 和 big-endian 与此 prefix/postfix 符号相关.
最后说一句,Reverse-Polish notation得到Dijkstra的大力支持(他是短路优化的强烈反对者,被认为是Reverse-Polish notation的发明者)。支持不支持是你的选择(我不支持)。
我所说的使用是指它在许多计算器中的使用,例如 HP35-
我的猜测(和困惑)是 -
- postfix 实际上是更有效的内存 -(SO post 评论 here)。 (混淆 - 两者的评估算法与堆栈相似)
- 当时计算器中的键盘输入类型(混淆 - 这应该无关紧要,因为它只取决于首先或最后给出的运算符的顺序)
可以问这个问题的另一种方式是 postfix notation 相对于 prefix 有什么优势?
谁能赐教一下?
一方面,更容易实施评估。
有了前缀,如果你推送一个操作符,然后是它的操作数,你需要知道操作符何时拥有它的所有操作数。基本上,您需要跟踪您推送的运算符何时拥有所有操作数,以便您可以展开堆栈并进行评估。
由于一个复杂的表达式最终可能会在堆栈上有许多运算符,因此您需要一个可以处理此问题的数据结构。
例如,这个表达式:- + 10 20 + 30 40
将同时在堆栈上有一个 -
和一个 +
,对于每个,你需要知道你是否有可用的操作数。
带后缀,当你压入一个运算符时,操作数(应该)已经在堆栈上,只需弹出操作数并计算。你只需要一个可以处理操作数的栈,不需要其他数据结构。
基本上,因为如果您在 postfix 中编写表达式,您可以仅使用 Stack:
读取表达式的下一个元素,
如果是操作数,则入栈,
否则从Operation需要的Stack中读取操作数,并将结果压入Stack。
如果不是表达式结束,转到1。
例子
expression = 1 2 + 3 4 + *
stack = [ ]
Read 1, 1 is Operand, Push 1
[ 1 ]
Read 2, 2 is Operand, Push 2
[ 1 2 ]
Read +, + is Operation, Pop two Operands 1 2
Evaluate 1 + 2 = 3, Push 3
[ 3 ]
Read 3, 3 is Operand, Push 3
[ 3 3 ]
Read 4, 4 is Operand, Push 4
[ 3 3 4 ]
Read +, + is Operation, Pop two Operands 3 4
Evaluate 3 + 4 = 7, Push 7
[ 3 7 ]
Read *, * is Operation, Pop two Operands 3 7
Evaluate 3 * 7 = 21, Push 21
[ 21 ]
如果您希望您的人类阅读顺序与机器基于堆栈的评估顺序相匹配,那么 postfix 是一个不错的选择。
也就是说,假设您是从左到右阅读的,并不是每个人都这样做(例如希伯来语、阿拉伯语……)。并假设您的机器使用堆栈进行评估,但并非所有人都这样做(例如术语重写 - 参见 Joy)。
另一方面,当机器评估 "back to front/bottom-to-top" 时,人类偏好的前缀没有任何问题。如果关注的是评估 as 令牌到达,序列化也可以逆转。工具帮助在前缀表示法中可能会更好(首先了解 functions/words 可能有助于确定有效参数的范围),但您始终可以从右到左 type。
我认为这只是一个约定...
前缀表示法可能更常用...在数学中,在 F(x,y) 等表达式中。这是一个非常古老的约定,但与许多旧系统(英尺和英寸、信纸)一样,与我们使用更精心设计的系统所能做的相比,它有缺点。
几乎每一年的大学数学教科书都至少要浪费一页来解释f(g(x))
意味着我们先申请g
然后f
。按阅读顺序这样做更有意义:x.f.g
意味着我们首先应用 f
。那么如果我们想申请 h
"after" 我们就说 x.f.g.h
.
例如,考虑我最近不得不处理的 3d 旋转问题。我们想根据 XYZ 约定旋转矢量。在 postfix 中,操作是 vec.rotx(phi).roty(theta).rotz(psi)
。使用前缀,我们必须重载 *
或 ()
然后反转操作顺序,例如 rotz*roty*rotx*vec
。当您想考虑更大的问题时,这很容易出错并且不得不一直考虑它。
例如,我在别人的代码中看到类似rotx*roty*rotz*vec
的东西,我不知道这是一个错误还是不寻常的ZYX轮换约定。我还是不知道。该程序有效,因此它在内部是自洽的,但在这种情况下,前缀表示法使其难以维护。
前缀表示法的另一个问题是,当我们(或计算机)解析表达式 f(g(h(x)))
时,我们必须将 f
保存在内存中(或堆栈中),然后 g
,然后 h
,然后 ok 我们可以将 h
应用于 x
,然后我们可以将 g
应用于结果,然后 f
应用于结果。与 x.f.g.h
相比,内存中的东西太多了。在某些时候(对于人类来说比计算机要快得多)我们会 运行 内存不足。那样的失败并不常见,但为什么在 x.f.g.h
不需要短期记忆的情况下还要打开那扇门。就好比递归和循环的区别
还有一件事:f(g(h(x)))
有很多括号,它开始看起来像 Lisp。就运算符优先级而言,后缀表示法是明确的。
一些数学家(尤其是 Nathan Jacobson)曾尝试改变约定,因为后缀在顺序非常重要的非交换代数中更容易使用,但收效甚微。但是既然我们有机会在计算方面做得更好,我们应该抓住这个机会。
两种符号的离线评估在理论机器中是相同的
(Eager evaluation strategy)仅用一个堆栈进行评估(不将运算符放入堆栈)
可以通过评估前缀符号 right-to-left
.
- 7 + 2 3
# evaluate + 2 3
- 7 5
# evaluate - 7 5
2
与计算后缀符号相同 left-to-right
.
7 2 3 + -
# put 7 on stack
7 2 3 + -
# evaluate 2 3 +
7 5 -
# evaluate 7 5 -
2
(优化的短路策略)使用两个堆栈(一个用于运算符,一个用于操作数)进行评估
可以通过评估前缀符号 left-to-right
.
|| 1 < 2 3
# put || in instruction stack, 1 in operand stack or keep the pair in stack
instruction-stack: or
operand-stack: 1
< 2 3
# push < 2 3 in stack
instruction-stack: or, less_than
operand-stack: 1, 2, 3
# evaluate < 2 3 as 1
instruction-stack: or
operand-stack: 1, 1
# evaluate || 1 1 as 1
operand-stack:1
请注意,我们可以在这里轻松地对 boolean
表达式进行 短路优化(与之前的评估序列相比)。
|| 1 < 2 3
# put || in instruction stack, 1 in operand stack or keep the pair in stack
instruction-stack: or
operand-stack: 1
< 2 3
# Is it possible to evaluate `|| 1` without evaluating the rest ? Yes !!
# skip < 2 3 and put place-holder 0
instruction-stack: or
operand-stack: 1 0
# evaluate || 1 0 as 1
operand-stack: 1
与计算后缀符号相同 right-to-left
.
(优化的短路策略)用一个带元组的堆栈进行评估(同上)
可以通过评估前缀符号 left-to-right
.
|| 1 < 2 3
# put || 1 in tuple-stack
stack tuple[or,1,unknown]
< 2 3
# We do not need to compute < 2 3
stack tuple[or,1,unknown]
# evaluate || 1 unknown as 1
1
与计算后缀符号相同 right-to-left
.
人工从左到右输入数据时在计算器中进行在线评估
在计算器中输入数字时,后缀记法 2 3 +
可以立即求值,而人类将要输入的符号一无所知。它与前缀表示法相反,因为当我们有 - 7 +
时我们无事可做,直到我们得到像 - 7 + 2 3
.
人工从右到左输入数据时在计算器中进行在线评估
现在 Prefix-notation 可以立即评估 + 2 3
,而 Postfix-notation 等待进一步输入时有 3 + -
.
请参考@AshleyF,注意阿拉伯语是从右到左书写的,而英语是从左到写的!
我猜 little-endian 和 big-endian 与此 prefix/postfix 符号相关.
最后说一句,Reverse-Polish notation得到Dijkstra的大力支持(他是短路优化的强烈反对者,被认为是Reverse-Polish notation的发明者)。支持不支持是你的选择(我不支持)。