实现一个简单的计算器
2013-10-08 00:25
615 查看
小标题:一个计算器引发的血案!
故事:几个星期前,我和我的小伙伴一起去华联超市买东西。它想去买菜,我想去买零食,我们说好了不买其他东西。不幸的是,我们高高兴兴的从超市的一楼逛到三楼的时候,小车已经不能装下更多的东西了。我们互喷了一句,很不高兴的付了账,很高兴的把东西搬了回去。
当回去分赃的时候,我发现一个问题:我的小伙伴刷卡付账,这意味着我必须把钱给他。尽管我非常不想给它钱,但是还是要给它钱。我拿出账单的时候,惊呆了:账单密密麻麻足足有两米!这是不是意味着,等我算清楚的时候,小伙伴已经老死了。当然,聪明的我决不会用手指算这种垃圾。本人经过慎重研究,考虑到计算的重复性特征,决定采用21世纪西方最先进的计算机技术,完成小伙伴死之前还它钱的伟大壮举!
于是就又有了这个看起来不牛叉,实际上也不牛叉的计算器!We call it Calc-V1。
用法:输入一串加减乘除的四则运算表达式,得到这个表达式的结果。
输入:合法的四则运算表达式,包括:+ - * / () 和取反;识别整数、十六进制数、小数(不包括科学计数法格式)
输出:计算结果(小数或者整数)
要求:正确计算+ - * / () 取反,正确识别整数、小数(不包括科学计数法,因为华联超市的账单上没有使用科学计数法)。不识别不正确的表达式。
分析:四则运算的计算机实现一点都不难,因为计算机本身就支持四则运算中的所有操作(+-*/),这里的主要问题是如何正确识别运算优先级,从而正确计算结果。
优先级无非是:先乘除后加减,先括号后外边。
如果用推导式表达的话(参考编译原理的书籍),如下:
Expression >> expression + add-item
Expression >> expression - add-item
Expression >> add-item
Add-item >> add-item * multi-item
Add-item >> add-item / multi-item
Add-tiem >> multi-item
Multi-item >> number
Multi-item >> (expression)
Multi-item >> -(expression)
Multi-item >> +(expression)
这个文法(推导系统)是左递归的,消除左递归后,可以被自上而下的LL(1)分析所识别(参考编译原理)。如下(null代表空):
Expression >> add-item add-expression
Add-expression >> + add-item add-expression
Add-expression >> - add-item add-expression
Add-expression >> null
Add-item >> multi-item multi-expression
Multi-expression >> * multi-item multi-expression
Multi-expression >> / multi-item multi-expression
Multi-expression >> null
Multi-item >> number
Multi-item >> (expression)
Multi-item >> -(expression)
Multi-item >> +(expression)
可以构造LL(1)分析表来识别合法的四则运算表达式,然后使用语法制导翻译,解决四则运算优先级问题。(注:虽然我没有成功,语义分析做不下去了,但是这个方法肯定是可以的,只是语义计算可能有点复杂。)
作为一个正常的人,遇到这样的语义问题,肯定会放弃,就和我做的一样。放弃后,我唯一觉得不开心的是:不就是计算加减乘数吗,上帝有必要把它搞这么复杂吗?
人类一思考,上帝就发笑。就在我觉得这事情不可理喻的时候,脑子里突然一道灵光:我找到了,就像牛顿头上的烂苹果。所谓伟大的。。。
其实,事情是这样的,大概在很多个日子之前,有个什么物质(文章、或者中国人)提过到如何用栈实现有优先级的四则运算。大致的思路是:维护一个操作数的栈和运算符号的栈,如果当前读取到的运算符号比较性感,就会吸引该运算符前面的操作数,使它不会投入到前一个运算符的怀抱;相反,如果当前运算符看起来非常屌丝,就会吓跑它前面的操作数,使该操作数和前一个运算符结合。
例如:1+2+3,Calc-V1会一直读取操作数和运算符,直到读取到第二加号时,奇妙的事情就发生了,这个加号和第一个加号是同一个优先级的,也就是说他们长得一样帅,于是第二个加号先生让我们的2小姐很不高兴:不仅长得丑,约会还迟到,屌丝!于是2就和第一个加号牵手,和1完成了加法运算,计算结果放回原位置,变成3+3。
再比如:1+2*3,Calc-V1会一直读取操作数和运算符,直到读取到*时,奇妙的事情就发生了。*可不是臭加号,它拥有钻石的外表,高富帅的风度。2小姐当然不是傻子,她甩开哪怕是先到的加号先生的手,毅然决然的奔向*哥哥!当然,3还没有到来,不过天已经注定了他和2小姐的爱情!
所以四则运算就是谁帅谁性感的问题,真是俗!
下面是代码,为了方便(不是为了‘方便’,你们太邪恶了),采用perl语言,脚本语言都很好理解,没学过也照样看得懂。如果想改写成其他语言,照葫芦画瓢就好了。
说明:在完成屌丝和高富帅的战争之前,有一个简单的词法分析,也就是程序把一个表达式识别成一个个的单词,而不是一个个字符。比如:123+234,应该是’123’和’+’和’234’,而不是1和2和3等等。解释注释都是掩饰,上代码!
#!/usr/bin/perl -w ### tools func ### $local_debug = 1; sub Info($){ print "[INFO ]@_\n"; } sub Die($) { die "@_\n"; } sub Err($) { print "[ERROR]@_\n"; } sub De($) { $local_debug == 0 or print "[DEBUG]@_\n"; } ### end ### ### global variables @buf = ();#the string char buffer @back = ();# the unused token $status = "value";# the status of the operation @stack = ();# the stack to keep operators and numbers $base = 0;# the base priority of new tokens $offset = 3;# the priority of an () will improved $unary_op = "";# the unary + or -, to support +12, -4.5 ### global variables ### main start if(@ARGV > 0){#read from cmd line $string = join("",@ARGV); $res = &calc($string); print "=$res\n"; }else{ while(<STDIN>){#read from pipeline or cmd line if(/^\s*$/ or /^#/ ){ next; } $res = &calc($_); print "$res=$_\n"; } } exit; ### main end ### define # operator: + - * / +( -( ( ) # # priority: 0 0 1 1 2 2 2 2 -1 # the '#' is the end of token string # number: int[12 13 15] float:[0.1 1.1 1. ] hex[0x00 0X0F] # token attribute: + - op_plus, * / op_multi, ( +( -( op_left, ) op_right, int num_int, float num_float, hex num_hex ### end define sub calc{ if( @_ == 0){ return ;} my $string = shift@_; #init @buf = split(//, $string); $status = "value";# the status transfer table: value -> operator, operator -> value, value -> number -> operator @back = ();#buffer the unused token @stack = ();#all operators and numbers are inserted in this stack @priority = ();#all token's priority are inserted in this stack $base = 0; $unary_op = ""; #run while(1){#status transfer machine De "stats: $status"; my @token = &get_token(); if( $token[1] eq "ERROR"){ Die "unexpected char:$token[0]"; } De "token:@token"; if($status eq "value"){ &actionValue(@token);} elsif($status eq "number"){ &actionNumber(@token); } elsif($status eq "operator"){ &actionOperator(@token);} elsif($status eq "end"){ last; } else{ Die "unexpected status:$status"; } De "stack:@stack"; #De "prior:@priority"; } return $stack[0]; } sub get_priority{ my $a = $_[0]; if($a eq "+" or $a eq "-"){ return 0; } elsif($a eq "*" or $a eq "/"){ return 1; } elsif($a =~ /\(/ or $a eq ")" ){ return 2; } elsif($a eq "#"){ return -1; } else{ Die "bad token to ask for priority:$a"; } } sub actionValue{ my($token, $attr) = @_; if( $attr =~ /^num/ ){ $token = $attr eq "num_hex" ? hex($token) : 1*$token; push@stack, $token; push@priority, 0;#the number's priority is ignored $status = "operator"; } elsif ( $token eq "+" || $token eq "-" ){ $unary_op = $token;# needs a number to build a value, +12,-2.3 etc. $status = "number"; } elsif ( $token eq "+(" || $token eq "-(" || $token eq "("){ push@stack, $token; push@priority, $base+&get_priority($token); $base += $offset;#improve the priority $status = "value";#alse needs a value }else{ Die "unexpected token: $token, where needs a value or its prefix:+-"; } } sub actionNumber{ my($token, $attr) = @_; if( $attr =~ /^num/ ){ $token = $attr eq "num_hex" ? hex($token) : 1*$token; if($unary_op eq "-"){ $token = -1*$token; } elsif($unary_op eq "+"){} else{ Die "unexpected unary operator:$unary_op"; } push@stack, $token; push@priority, 0; # the number's priority is ignored $status = "operator"; }else{ Die "unexpected token: $token, where needs a number for its prefix:$unary_op"; } } sub actionOperator{ my($token, $attr) = @_; if ( $token eq "+" || $token eq "-" || $token eq "*" || $token eq "/"){ if(&actionCalc($token) eq "no_action"){# no action means to shift in the token push@stack, $token; push@priority, $base+&get_priority($token); $status = "value"; }else{ #the action will change the status in the @stack, use the same token try again @back = ($token, $attr); $status = "operator"; } } elsif ( $token eq ")" ){ if(&actionCalc($token) ne "matched"){ @back = ($token, $attr); }else{ $base -= $offset; $base >=0 or Die "two many ')'"; } $status = "operator"; } elsif( $token eq "#"){#means the end of the string $base == 0 or Die "unexpected end of calc string, needs more right parentheses"; if(&actionCalc($token) eq "no_action"){ @stack == 1 or Die "bad end of stack:@stack"; $status = "end"; }else{ @back = ($token, $attr); $status = "operator"; } }else{ Die "unexpected token: $token, where needs an op"; } } sub actionCalc{ my $next_operator = $_[0]; my $next_priority = $base + &get_priority($next_operator); $next_priority -= $next_operator eq ")" ? $offset : 0; if(@stack < 3){ return "no_action"; }#at least has this pattern: number operator number my $current_priority = $priority[-2];# get the last operator if($current_priority >= $next_priority){ if(&is_binary_op($stack[-2])){ my $value = &oneOPtwo($stack[-3], $stack[-2], $stack[-1]); pop@stack;pop@stack;pop@stack;push@stack, $value; pop@priority;pop@priority;pop@priority;push@priority,0; return "calc"; ae30 }elsif( &is_left_parentheses($stack[-2])){ my $value = pop@stack; my $op = pop@stack; $value = $op eq "-(" ? -1*$value : $value; push@stack, $value; pop@priority;pop@priority;push@priority,0; return "matched"; }else{ Die "bad stack content at:$stack[-2]"; } }else{ return "no_action"; } } sub is_binary_op{ my $op = shift@_; return $op eq "+" || $op eq "-" || $op eq "*" || $op eq "/"; } sub is_left_parentheses{ my $op = $_[0]; return $op eq "+(" || $op eq "-(" || $op eq "("; } sub oneOPtwo{ my($one, $op, $two) = @_; De "calc : $one $op $two"; my $res = 0; if( $op eq "+" ){ $res = $one + $two; } elsif( $op eq "-" ){ $res = $one - $two; } elsif( $op eq "*" ){ $res = $one * $two; } elsif( $op eq "/" ){ $res = $one / $two; } else{ Die "bad op : $op"; } return $res; } ### end ### token func sub get_token{ my @token = ("#", "#"); if( @back != 0 ){ @token = @back; @back = (); return @token; } #ignore whitespace while( @buf > 0 && &is_whitespace($buf[0]) ){ shift@buf; } if( @buf > 0){ my $ch = shift@buf; if( $ch eq "+" || $ch eq "-" ){ if( @buf > 0 && $buf[0] eq "(" && $status eq "value"){ shift@buf; @token = ("$ch(", "op_left"); }else { @token = ($ch, "op_plus"); } }elsif( $ch eq "*" ){ @token = qw(* op_multi); } elsif( $ch eq "/" ){ @token = qw(/ op_multi); } elsif( $ch eq "(" ){ @token = qw%( op_left%; } elsif( $ch eq ")" ){ @token = qw%) op_right%; } elsif( &is_digit($ch) ){ @token = &get_digit($ch); } else { Err "unexpected char: '$ch'"; @token = ($ch, "ERROR");} } return @token; } sub get_digit{ my @num = (); push@num, $_[0]; # handle hex-format: 0x00 if( $num[0] eq "0" && @buf > 1 && ($buf[0] eq "x" || $buf[0] eq "X") && &is_hexdigit($buf[1]) ){ shift@buf; @num = qw(0 x); while( @buf > 0 && &is_hexdigit($buf[0]) ){ push@num, $buf[0]; shift@buf; } my $token = join "", @num; return ($token, "num_hex"); } while( @buf > 0 && &is_digit($buf[0]) ){ push@num, $buf[0]; shift@buf; } # if is not . if( @buf == 0 || @buf > 0 && $buf[0] ne "." ){ my $token = join "", @num; return ($token, "num_int"); } # handle . push@num, $buf[0]; shift@buf; while( @buf > 0 && &is_digit($buf[0]) ){ push@num, $buf[0]; shift@buf; } my $token = join "", @num; return ($token, "num_float"); } sub is_whitespace{ my $ch = $_[0]; return $ch eq " " || $ch eq "\t" || $ch eq "\n" || $ch eq "\f" || $ch eq "\r" ; } sub is_digit{ my $ch = $_[0]; return $ch eq "0" || $ch eq "1" || $ch eq "2" || $ch eq "3" || $ch eq "4" || $ch eq "5" || $ch eq "6" || $ch eq "7" || $ch eq "8" || $ch eq "9"; } sub is_hexdigit{ my $ch = $_[0]; return $ch eq "0" || $ch eq "1" || $ch eq "2" || $ch eq "3" || $ch eq "4" || $ch eq "5" || $ch eq "6" || $ch eq "7" || $ch eq "8" || $ch eq "9" || $ch eq "a" || $ch eq "b" || $ch eq "c" || $ch eq "d" || $ch eq "e" || $ch eq "f" || $ch eq "A" || $ch eq "B" || $ch eq "C" || $ch eq "D" || $ch eq "E" || $ch eq "F"; }
有了这个神器,妈妈再也不用担心我的学习了。开玩笑。有了这个Calc-V1,以后再也不用担心计算中途的时候,该死的鼠标点错了啊(是不是很开心)。
故事结局:当我找出编译原理的书,倒腾出推导式,消除左递归,完成LL分析发现困难重重,又被烂苹果砸到脑袋,采用perl,在linux下完成编码和测试,最后计算出正确答案的时候,悄悄的,北京的房价又涨了几千,人民币又贬值了几块,太阳东升西落了几次。
微风吹开我的长发,我拿着写着结果的纸片,轻盈的走向我的小伙伴,它,它,它,居然不理我了!不理我了!居然不理我了!!!(旁观者眼中:主角得不到小伙伴的认可,吐血身亡。)
画外音:天才都短命。珍爱生命,远离天才。
相关文章推荐
- 用python实现一个简单的计算器
- 利用ANTLR4实现一个简单的四则运算计算器
- 一个简单的关于计算器功能的实现(剽窃)
- Java实现一个简单的计算器(流式布局)
- shell实现一个简单的计算器功能小脚本
- 用javascrip实现一个简单的加减乘除计算器
- 用ASP.NET实现一个简单的计算器(适合入门者)
- C# 一步一步完成一个简单的计算器 第三步---实现双目运算
- jsp实现一个简单的计算器
- J2ME应用实例——一个简单的计算器实现(附源代码)
- 编译原理:用flex和bison实现一个简单的计算器
- 用Visual studio2005的单元测试框架实现一个简单的计算器
- qt实现一个简单的计算器
- C语言实现一个简单的计算器
- IOS 实现一个简单的计算器
- JS小Demo实战之一个超级简单的计算器功能的实现。
- QT实现一个简单的计算器
- MFC实现一个简单的计算器
- jQuery实现一个简单的计算器
- 一个简单的计算器的实现(C++)