逆波兰表示法中的变长运算符后缀

如何解决逆波兰表示法中的变长运算符后缀

背景:在传统的逆波兰表示法中,所有运算符都必须具有固定长度,这使得 RPN 很容易被代码评估和操作,因为每个标记、表达式和子表达式都是“自包含的” " 这样就可以盲目地将 y 中的 x y * 替换为 y 1 + 以得到 x y 1 + *,这是另一种有效的表达式,它完全符合您的要求。这是一个带有命名变量支持的简单 RPN 计算器的交互式演示。请注意,演示试图展示算法的要点;它们与生产代码无关,也不代表生产代码。

var rpn = prompt("Please enter RPN string,where each token is " +
  "separated by a space","x 1 x + * 2 /").trim().split(/\s+/);

var stack = [],variables = [],values = [];
for (let i = 0,len = rpn.length|0; i < len; i=i+1|0) {
    if (/^\d*(\.\d*)?$/.test(rpn[i]) && rpn[i] !== "") {
        stack.push( rpn[i] );
    } else if (/^[a-z]$/i.test(rpn[i])) {
        stack.push( rpn[i] );
        if (!~variables.indexOf(rpn[i])) variables.push( rpn[i] );
    } else {
        if(stack.length<2)throw Error("No operand for " + rpn[i]);
        const firstPop = stack.pop(); //lacks check if stack empty
        stack.push( "(" + stack.pop() + rpn[i] + firstPop + ")" );
    }
}
if (stack.length !== 1) throw Error("Invalid RPN got: " + stack);

for (let i = 0,len = variables.sort().length|0; i < len; i=i+1|0)
    values[i] = +prompt(variables[i] + " = ",Math.random()*10|0);

variables.push("'use strict';return(" + stack.pop() + ")");
alert("Result: " + Function.apply(0,variables).apply(0,values));

问题:如何修改或调整 RPN 以适应可变长度的“运算符”(想想函数)?

研究和建议的解决方案:在最终确定为指定的代码语言之前,我使用 RPN 作为代码的中间表示。我想尽可能多地保留 RPN 的有用性和易用性,同时仍然表示可变长度运算符。我设计了三个解决方案,并在下面相当简单的演示中实现了它们。

  1. 一个特殊的 ARGUMENTS_BEGIN 前缀运算符(我们将在此问题中使用 #)。该解决方案与传统 RPN 背道而驰,因为它添加了前缀运算符来表示参数开始的位置。这使得参数列表的大小自动扩展,并有助于调试,因为格式错误的标记替换不会破坏参数列表,从而更容易定位错误。由于需要更多代码来处理嵌套函数调用等情况,这可能会使参数的操作变得更加复杂,但我不完全确定可能会出现什么复杂情况。我猜我会遇到解析包含前缀和后缀运算符的语法的障碍。它还使直接评估变得更加困难,因为需要回溯或单独的堆栈来定位参数的开头。

var rpn = prompt("Please enter a RPN string,"# # x 210 gcd x 6 * 126 gcd").trim()
  .split(/\s+/);

var stack = [],len = rpn.length|0; i < len; i=i+1|0) {
    if (/^\d*(\.\d*)?$/.test(rpn[i]) && rpn[i] !== "") {
        stack.push( rpn[i] );
    } else if (/^[a-z]$/i.test(rpn[i])) {
        stack.push( rpn[i] );
        if (!~variables.indexOf(rpn[i])) variables.push( rpn[i] );
    } else if (/^[a-z]\w*$/i.test(rpn[i])) {
        const s = stack.lastIndexOf("#");
        if(s<0) throw Error("No start of arguments to " + rpn[i]);
        stack.push( rpn[i]+"(" + stack.splice(s).slice(1) + ")" );
    } else if (rpn[i] === '#') {
        stack.push( '#' ); // sparks a syntax error if misused
    } else {
        if(stack.length<2)throw Error("No operand for " + rpn[i]);
        const firstPop = stack.pop();
        stack.push( "(" + stack.pop() + rpn[i] + firstPop + ")" );
    }
}
if (stack.length !== 1) throw Error("Invalid RPN got: " + stack);

for (let i = 0,Math.random()*10|0);

variables.push( "gcd" );
values.push( function gcd(a,b) {return b ? gcd(b,a % b) : a;} );

variables.push("'use strict';return(" + stack.pop() + ")");
alert("Result: " + Function.apply(0,values));

  1. 逗号运算符将参数组合在一起(我们将使用 , 对最后两个项目进行分组,并使用 ~ 表示本问题中的零长度组)。这个解决方案是传统的 RPN,只是对逗号和零组运算符的处理稍有特殊。每个变长运算符都被视为长度为 1(零参数用 ~ 表示)。逗号从两个项目中构建参数列表,每个项目都可以是普通标记、参数列表或零组运算符。优点包括易于操作和解析代码,符合 RPN 的简单性,以及保留 RPN 的令牌独立性。缺点包括 RPN 更难调试,因为一个微小的畸形令牌可能会扰乱整个参数列表并且滚雪球失控,无法检测它是故意还是意外。

var rpn = prompt("Please enter RPN string,"x 6 * 126,210,gcd ~ PI %")
  .trim().split(/\s+/);

var stack = [],len = rpn.length|0; i < len; i=i+1|0) {
    if (/^\d*(\.\d*)?$/.test(rpn[i]) && rpn[i] !== "") {
        stack.push( rpn[i] );
    } else if (/^[a-z]$/i.test(rpn[i])) {
        stack.push( rpn[i] );
        if (!~variables.indexOf(rpn[i])) variables.push( rpn[i] );
    } else if (/^[a-z]\w*$/i.test(rpn[i])) {
        if(stack.length<1)throw Error("No operand for " + rpn[i]);
        stack.push( rpn[i] + "(" + stack.pop() + ")" );
    } else if (rpn[i] === ',') {
        if(stack.length<2)throw Error("No operand for " + rpn[i]);
        const p2 = "" + stack.pop(),p1 = "" + stack.pop();
        stack.push( p1 && p2 ? p1 + "," + p2 : p1 || p2 );
    } else if (rpn[i] === '~') {
        stack.push( "" ); // zero-length group
    } else {
        if(stack.length<2)throw Error("No operand for " + rpn[i]);
        const firstPop = stack.pop(); //lacks check if stack empty
        stack.push( "(" + stack.pop() + rpn[i] + firstPop + ")" );
    }
}
if (stack.length !== 1) throw Error("Invalid RPN got: " + stack);

for (let i = 0,Math.random()*10|0);

variables.push( "gcd","PI" );
values.push( function gcd(a,a % b) : a;} );
values.push( function PI() {return Math.PI;} );

variables.push("'use strict';return(" + stack.pop() + ")");
alert("Result: " + Function.apply(0,values));

  1. 运算符本质上存储它的长度(出于这个问题的目的,我们将在函数名称上附加一个数字)。该方案继承了传统RPN的所有优点。此外,它使解析器的阅读 方面变得简单。此外,调试更容易,因为不会意外插入新参数。但是,它使 RPN 代码的操作和生成更加复杂。更新和生成参数列表很困难,因为该解决方案偏离了 RPN 的令牌独立性方面,因此添加参数(并更改参数)需要两个动作和一个查找(与传统的一个动作和零查找相反): (1.) 插入参数,(2.) 查找变长运算符的位置,以及 (3.) 更新运算符的长度。

var rpn = prompt("Please enter RPN string,"x 210 gcd2 x 6 * 126 gcd3").trim()
  .split(/\s+/);

var stack = [],len = rpn.length|0,m; i < len; i=i+1|0) {
    if (/^\d*(\.\d*)?$/.test(rpn[i]) && rpn[i] !== "") {
        stack.push( rpn[i] );
    } else if (/^[a-z]$/i.test(rpn[i])) {
        stack.push( rpn[i] );
        if (!~variables.indexOf(rpn[i])) variables.push( rpn[i] );
    } else if (m = rpn[i].match(/^([a-z]+)(\d+)$/i)) {
       if(stack.length<m[2])throw Error("No operand for "+rpn[i]);
        stack.push( m[1] + "(" + stack.splice(-m[2]) + ")" );
    } else {
        if(stack.length<2)throw Error("No operand for " + rpn[i]);
        const firstPop = stack.pop(); //lacks check if stack empty
        stack.push( "(" + stack.pop() + rpn[i] + firstPop + ")" );
    }
}
if (stack.length !== 1) throw Error("Invalid RPN got: " + stack);

for (let i = 0,values));

  1. 堆栈上的嵌套数组(无法进行演示)。该解决方案涉及在堆栈上的运算符之前将参数存储在列表中,这使得代码的直接执行非常容易。然而,这违反了 RPN 的整个原则和优势,即拥有一个扁平的项目列表。或许,如果列表只有一层,问题就不会太大;但是,对于我的用例,我最终会得到深度嵌套的列表。因此,RPN 的操作和 RPN 的生成变得非常困难。

单个问题的推断:这个问题还有其他可能的解决方案吗?这个问题的标准(最常用)解决方案是什么?我的解决方案是否存在根本问题(请提供反例)?我是否忽略了我的解决方案的一些优点/缺点?我的解决方案的算法可以改进吗?

解决方法

我不确定您的计划是(曾经)将您实现的每个函数视为具有其独特元数的单独运算符,还是使用一个“函数调用”运算符从求值器的操作数中提取所需数量的参数调用函数前栈。

如果是后者,最直接的逆波兰转换可能来自:

名称(expr1,expr2...exprN)

为此:

name expr1 expr2...exprN N callFunc

请记住,任何“exprX”都可能是任意复杂的,包括它自己的函数调用。没关系;到“callFunc”到达时,您只需要担心操作数堆栈中最上面的 N+2 项。最棘手的一点是跟踪实际存在的参数数量,并确保计数在“callFunc”之前进入 RPN。

这需要某种堆栈来解释嵌套函数,但除此之外并不太困难。实际上可以使用运算符堆栈(将计数保持在“callFunc”运算符的“下方”,一个已知的偏移量,并在每次遇到逗号时更新它。这自然会处理函数嵌套,但这不是唯一的方法) .

在执行过程中,“callFunc”接受一个参数 N,即从操作数堆栈中取出的参数数量。您可以将它们放入一个列表或数组中,一旦您将其拉出并调用它(很可能间接使用某种字典),您就可以将其传递给“name”。

为了完整起见,您可能希望在解析时进行错误检查,以确保所调用函数的参数数量和类型正确(您可以将该信息保存在指向评估函数的代码的同一字典中)功能)。还要注意逗号出现在它们不应该出现的地方,就像所有格式错误的表达式一样。然后评估者可以愉快地进行,而不必担心任何这些。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-