将中缀表达式转换为后缀表示法


23

当我看到这个封闭问题的标题时,我认为它看起来像是一个有趣的高尔夫挑战代码。因此,让我这样介绍一下:

挑战:

编写一个程序,表达式或子例程,给定一个用infix表示的算术表达式,例如1 + 2,以后缀表示即输出相同的表达式1 2 +

(注意:类似的挑战在1月初发布。 但是,我确实觉得这两个任务在细节上有很大不同,足以证明这个单独的挑战是正确的。而且,我只在键入下面的所有内容后才注意到另一个线程,我宁愿不只是把它扔掉。)

输入:

输入包括由以下组成的有效缀算术表达式的数目(非负整数表示为一个或多个十进制数字序列),平衡括号来指示分组的子表达式,和四个缀二进制运算符 +-*/。这些中的任何一个都可以由任意数量的空格字符分隔(并且整个表达式都被包围),应将其忽略。1个

对于那些喜欢形式语法的人,这里有一个简单的类似于BNF的语法,定义了有效的输入。为了简洁起见,语法不包含可选空格,该空格可能出现在任意两个标记之间(数字中的数字除外):

expression     := number | subexpression | expression operator expression
subexpression  := "(" expression ")"
operator       := "+" | "-" | "*" | "/"
number         := digit | digit number
digit          := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

1空格的存在可能会影响解析的唯一情况是它们将两个连续的数字分开。但是,由于没有被运算符分隔的两个数字不会出现在有效的中缀表达式中,因此这种情况永远不会出现在有效的输入中。

输出:

输出应该是与输入等效的后缀表达式。输出表达式应仅由数字和运算符组成,每对相邻标记之间必须有一个空格字符,如以下语法(确实包含空格)2所示

expression  := number | expression sp expression sp operator
operator    := "+" | "-" | "*" | "/"
number      := digit | digit number
digit       := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
sp          := " "

2再次为简单起见,number即使下面的规则在输出中禁止使用前导零,该语法中的乘积也接受前导零。

运算符优先级:

在没有括号的情况下,适用以下优先级规则:

  • 运算符*/具有比+和高的优先级-
  • 运算符*/彼此具有相同的优先级。
  • 运算符+-彼此具有相同的优先级。
  • 所有运算符都是左关联的。

例如,以下两个表达式是等效的:

1 + 2 / 3 * 4 - 5 + 6 * 7
((1 + ((2 / 3) * 4)) - 5) + (6 * 7)

并且它们都应产生以下输出:

1 2 3 / 4 * + 5 - 6 7 * +

(这些都是一样的优先规则在C语言中来源于它大多数语言。他们可能像你教小学的规则,可能除了相对优先级*/)。

杂项规则:

  • 如果给定的解决方案是表达式或子例程,则应提供输入,并将输出作为单个字符串返回。如果解决方案是一个完整的程序,则应从标准输入中读取包含infix表达式的行,并将包含后缀版本的行打印至标准输出。

  • 输入中的数字可能包含前导零。输出中的数字不得带有前导零(数字0除外,该数字应输出为0)。

  • 不应以任何方式评估或优化表达式。特别是,您不应假定运算符必须满足任何关联,可交换或其他代数恒等式。也就是说,您不应该假设例如1 + 2equals 2 + 11 + (2 + 3)equals (1 + 2) + 3

  • 您可以假设输入中的数字不超过2 31 − 1 = 2147483647。

这些规则旨在确保正确的输出由输入唯一定义。

例子:

这是一些有效的输入表达式和相应的输出,形式"input" -> "output"如下:

"1"                  ->  "1"
"1 + 2"              ->  "1 2 +"
" 001  +  02 "       ->  "1 2 +"
"(((((1))) + (2)))"  ->  "1 2 +"
"1+2"                ->  "1 2 +"
"1 + 2 + 3"          ->  "1 2 + 3 +"
"1 + (2 + 3)"        ->  "1 2 3 + +"
"1 + 2 * 3"          ->  "1 2 3 * +"
"1 / 2 * 3"          ->  "1 2 / 3 *"
"0102 + 0000"        ->  "102 0 +"
"0-1+(2-3)*4-5*(6-(7+8)/9+10)" -> "0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -"

(至少,我希望所有这些都是正确的;我是手动进行转换的,所以错误可能会逐渐蔓延。)

为了清楚起见,以下输入均无效;它并不会不管你的解决方案,如果不给他们(虽然,当然,如返回的错误信息是不是更好,比如说,消耗的内存无限量):

""
"x"
"1 2"
"1 + + 2"
"-1"
"3.141592653589793"
"10,000,000,001"
"(1 + 2"
"(1 + 2)) * (3 / (4)"

Lisp这样的符号可以接受吗?例如,1 2 3 4 +意思是“ 1 + 2 + 3 + 4”。
Hauleth 2012年

3
@Hauleth:没有挑战,没有。此外,没有括号,您将如何解析1 2 3 4 + *
Ilmari Karonen 2012年

那么,otuput中不允许尾随空格(包括换行符)吗?
面包箱

@breadbox:尾随换行符可以。实际上,让我明确说明允许使用任何尾随空格。
Ilmari Karonen 2012年

对于最后一个有效的示例,我有一个输出为“ 0 1-2 3-4 * 5 6 7 8 + 9 /-10 + *-+”的解决方案,这对我来说似乎是正确的。你可以检查吗?(注意最后一个+运算符)
coredump

Answers:


8

Shell utils-60个字符

bc -c|sed -re's/[@iK:Wr]+/ /g;s/[^0-9]/ &/g;s/ +/ /g;s/^ //'

修复了各种问题,但是它变得更长了:(


1
这相当聪明,只是它似乎无法正确处理大于9的数字。
面包箱

@breadbox,sed -re's/[:@iKWr]+/ /g'将其修复为1个字符的费用。
ugoren

糟糕,尽管@ugoren的建议不起作用,因为连续的运算符之间不再有空格;我也必须针对此问题提出解决方案
Geoff Reedy 2012年

4

C,250 245 236 193 185个字符

char*p,b[99];f(char*s){int t=0;for(;*p-32?
*p>47?printf("%d ",strtol(p,&p,10)):*p==40?f(p++),++p:
t&&s[t]%5==2|*p%5-2?printf("%c ",s[t--]):*p>41?s[++t]=*p++:0:++p;);}
main(){f(p=gets(b));}

这是非高尔夫来源的可读版本,仍然反映了基本逻辑。它实际上是一个相当简单的程序。它唯一要做的实际工作是在遇到高关联性运算符时将低关联性运算符推到堆栈上,然后在该子表达式的“末端”将其弹出。

#include <stdio.h>
#include <stdlib.h>

static char buf[256], stack[256];
static char *p = buf;

static char *fix(char *ops)
{
    int sp = 0;

    for ( ; *p && *p != '\n' && *p != ')' ; ++p) {
        if (*p == ' ') {
            continue;
        } else if (*p >= '0') {
            printf("%ld ", strtol(p, &p, 10));
            --p;
        } else if (*p == '(') {
            ++p;
            fix(ops + sp);
        } else {
            while (sp) {
                if ((ops[sp] == '+' || ops[sp] == '-') &&
                        (*p == '*' || *p == '/')) {
                    break;
                } else {
                    printf("%c ", ops[sp--]);
                }
            }
            ops[++sp] = *p;
        }
    }
    while (sp)
        printf("%c ", ops[sp--]);
    return p;
}

int main(void)
{
    fgets(buf, sizeof buf, stdin);
    fix(stack);
    return 0;
}

通过删除保存字符if。例如if(!*p||*p==41)return p;s[++t]=*p;}->return*p&&*p-41?s[++t]=*p:p;
ugoren 2012年

K&R风格宣言:*f(p,s)char*p,s;{
ugoren

1.如果if测试失败,则返回错误。2.我知道,但是K&R函数decls是我画线的地方。我就是不能回到他们身边。
面包箱

我以为无论如何返回是在功能结束。}}和错过了for。但是,这里有一个改进:printf(" %ld"+!a,...
ugoren 2012年

1
另外,我认为您应该进行p全局设置(递归调用只是将被调用p方分配回调用方)。然后做f(p=gets(b))
ugoren

2

Bash w / Haskell w / C预处理器 sed,180 195 198 275

echo 'CNumO+O-O*fromInteger=show
CFractionalO/
main=putStr$'$*|sed 's/C\([^O]*\)/instance \1 String where /g
s/O\(.\?\)/a\1b=unwords\[a,b,\"\1\"];/g'|runghc -XFlexibleInstances 2>w

最后,它不再比C解决方案长。Haskell的关键部分几乎与BC解决方案一样懒...

将输入作为命令行参数。w如果您不喜欢将此文件更改为,则会创建带有ghc警告消息的文件runghc 2>/dev/null


1
晒干?(Bas h + H aske ll + s ed
CalculatorFeline

2

Python 2中,290 272 268 250 243 238个字节

现在终于比JS回答更短了!

这是一个完整的程序,使用调车场算法的基本实现。输入以带引号的字符串给出,结果打印到STDOUT

import re
O=[];B=[]
for t in re.findall('\d+|\S',input()):exec("O=[t]+O","i=O.index('(');B+=O[:i];O=O[i+1:]","while O and'('<O[0]and(t in'*/')<=(O[0]in'*/'):B+=O.pop(0)\nO=[t]+O","B+=`int(t)`,")[(t>'/')+(t>')')+(t>'(')]
print' '.join(B+O)

在线尝试!


说明:

我们需要做的第一件事是将输入转换为令牌。为此,我们找到了所有正则表达式匹配项,将它们\d+|\S大致翻译为“任何一组数字和任何非空格字符”。这将删除空格,将相邻数字解析为单个标记,并分别解析运算符。

对于调车场算法,我们需要处理4种不同的令牌类型:

  • ( -左括号
  • ) -右括号
  • +-*/ -运营商
  • 9876543210 -数字文字

幸运的是,这些ASCII码均按所示顺序分组,因此我们可以使用表达式(t>'/')+(t>')')+(t>'(')来计算令牌类型。结果是3代表数字,2代表运算符,1代表右括号, 0表示左括号。

使用这些值,之后我们exec根据令牌类型索引到大元组,以获取要执行的相应代码段。每个令牌都不同,这是调车场算法的基础。使用两个列表(作为堆栈):(O操作堆栈)和B(输出缓冲区)。运行完所有令牌后,将O堆栈上其余的运算符与输出缓冲区连接起来,并打印结果。


2

Prolog(SWI-Prolog),113字节

c(Z,Q):-Z=..[A,B,C],c(B,S),c(C,T),concat_atom([S,T,A],' ',Q);term_to_atom(Z,Q).
p(X,Q):-term_to_atom(Z,X),c(Z,Q).

在线尝试!

SWI Prolog具有比GNU Prolog更好的内置函数集,但是Prolog语法的冗长性仍然使它有些受阻。

说明

term_to_atom如果向后运行,则会将一个infix-notation表达式(作为原子存储)解析到一个解析树中(遵守通常的优先级规则,并删除前导零和空白)。然后,我们使用助手谓词c对解析树进行结构化递归,以深度优先的方式转换为后缀表示法。


1

Javascript(ES6),244个字节

f=(s,o={'+':1,'-':1,'*':2,'/':2},a=[],p='',g=c=>o[l=a.pop()]>=o[c]?g(c,p+=l+' '):a.push(l||'',c))=>(s.match(/[)(+*/-]|\d+/g).map(c=>o[c]?g(c):(c==')'?eval(`for(;(i=a.pop())&&i!='(';)p+=i+' '`):c=='('?a.push(c):p+=+c+' ')),p+a.reverse().join` `)

示例:
呼叫:f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
输出:(0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -带有尾随空格)

说明:

f=(s,                                                     //Input string
    o={'+':1,'-':1,'*':2,'/':2},                          //Object used to compare precedence between operators
    a=[],                                                 //Array used to stack operators
    p='',                                                 //String used to store the result
    g=c=>                                                 //Function to manage operator stack
        o[l=a.pop()]>=o[c]?                               //  If the last stacked operator has the same or higher precedence
            g(c,p+=l+' '):                                //  Then adds it to the result and call g(c) again
            a.push(l||'',c)                               //  Else restack the last operator and adds the current one, ends the recursion.
)=>                                                       
    (s.match(/[)(+*/-]|\d+/g)                             //Getting all operands and operators
    .map(c=>                                              //for each operands or operators
        o[c]?                                             //If it's an operator defined in the object o
            g(c)                                          //Then manage the stack
            :(c==')'?                                     //Else if it's a closing parenthese
                eval(`                                    //Then
                    for(;(i=a.pop())&&i!='(';)            //  Until it's an opening parenthese
                        p+=i+' '                          //  Adds the last operator to the result
                `)                                        
                :c=='('?                                  //Else if it's an opening parenthese
                    a.push(c)                             //Then push it on the stack
                    :p+=+c+' '                            //Else it's an operand: adds it to the result (+c removes the leading 0s)
        )                                                 
    )                                                     
    ,p+a.reverse().join` `)                               //Adds the last operators on the stack to get the final result

1

R,142字节

R能够自行解析,因此我们无需重新发明轮子,而是将解析器投入使用,它输出前缀表示法,并使用递归函数将其切换为后缀表示法。

f=function(x,p=1){
if(p)x=match.call()[[2]]
if((l=length(x))>1){
f(x[[2]],0)
if(l>2)f(x[[3]],0)
if((z=x[[1]])!="(")cat(z,"")
}else cat(x,"")
}

p参数是控制使用非标准的评价(R各地的程序员的克星),并有一些额外的if在那里s到控制支架的输出(这是我们希望避免)。

输入: (0-1+(2-3)*4-5*(6-(7+8)/9+10))

输出: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -

输入: (((((1))) + (2)))

输出: 1 2 +

另外,它可以与任意符号以及任何带有最多两个参数的预定义函数一起使用:

欧拉的身份

输入: e^(i*pi)-1

输出: e i pi * ^ 1 -

1至100之间的13股息

输入: which(1:100 %% 13 == 0)

输出: 1 100 : 13 %% 0 == which

婴儿鸡体重与时间的线性回归

输入: summary(lm(weight~Time, data=ChickWeight))

输出: weight Time ~ ChickWeight lm summary

最后一个示例可能超出了OP的范围,但是它确实使用了后缀表示法,因此...

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.