1. 程式人生 > >實現一個簡單的計算器

實現一個簡單的計算器

故事:

        幾個星期前,我和我的小夥伴一起去華聯超市買東西。它想去買菜,我想去買零食,我們說好了不買其他東西。不幸的是,我們高高興興的從超市的一樓逛到三樓的時候,小車已經不能裝下更多的東西了。我們互噴了一句,很不高興的付了賬,很高興的把東西搬了回去。

        當回去分贓的時候,我發現一個問題:我的小夥伴刷卡付賬,這意味著我必須把錢給他。儘管我非常不想給它錢,但是還是要給它錢。我拿出賬單的時候,驚呆了:賬單密密麻麻足足有兩米!這是不是意味著,等我算清楚的時候,小夥伴已經老死了。當然,聰明的我決不會用手指算這種垃圾。本人經過慎重研究,考慮到計算的重複性特徵,決定採用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+3Calc-V1會一直讀取運算元和運算子,直到讀取到第二加號時,奇妙的事情就發生了,這個加號和第一個加號是同一個優先順序的,也就是說他們長得一樣帥,於是第二個加號先生讓我們的2小姐很不高興:不僅長得醜,約會還遲到,屌絲!於是2就和第一個加號牽手,和1完成了加法運算,計算結果放回原位置,變成3+3

       再比如:1+2*3Calc-V1會一直讀取運算元和運算子,直到讀取到*時,奇妙的事情就發生了。*可不是臭加號,它擁有鑽石的外表,高富帥的風度。2小姐當然不是傻子,她甩開哪怕是先到的加號先生的手,毅然決然的奔向*哥哥!當然,3還沒有到來,不過天已經註定了他和2小姐的愛情!

       所以四則運算就是誰帥誰性感的問題,真是俗!

       下面是程式碼,為了方便(不是為了‘方便’,你們太邪惡了),採用perl語言,指令碼語言都很好理解,沒學過也照樣看得懂。如果想改寫成其他語言,照葫蘆畫瓢就好了。

       說明:在完成屌絲和高富帥的戰爭之前,有一個簡單的詞法分析,也就是程式把一個表示式識別成一個個的單詞,而不是一個個字元。比如:123+234,應該是’123’和’+’和’234’,而不是123等等。解釋註釋都是掩飾,上程式碼!

#!/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 = [email protected]_;

    #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;
        [email protected], $token;
        [email protected], 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 "("){
        [email protected], $token;
        [email protected], $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"; }
        [email protected], $token;
        [email protected], 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
            [email protected], $token;
            [email protected], $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]);
            [email protected];[email protected];[email protected];[email protected], $value;
            [email protected];[email protected];[email protected];[email protected],0;
            return "calc";
        }elsif( &is_left_parentheses($stack[-2])){
            my $value = [email protected];
            my $op = [email protected];
            $value = $op eq "-(" ? -1*$value : $value;
            [email protected], $value;
            [email protected];[email protected];[email protected],0;
            return "matched";
        }else{ Die "bad stack content at:$stack[-2]"; }
    }else{ return "no_action"; }
}
sub is_binary_op{
    my $op = [email protected]_;
    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]) ){ [email protected]; }
    if( @buf > 0){
        my $ch = [email protected];
        if( $ch eq "+" || $ch eq "-" ){ 
            if( @buf > 0 && $buf[0] eq "(" && $status eq "value"){ 
                [email protected];                                                                                                                                                        
                @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 = ();
    [email protected], $_[0];
    # handle hex-format: 0x00
    if( $num[0]  eq "0" && @buf > 1 && ($buf[0] eq "x" || $buf[0] eq "X") && &is_hexdigit($buf[1]) ){
        [email protected];
        @num = qw(0 x);
        while( @buf > 0 && &is_hexdigit($buf[0]) ){ 
            [email protected], $buf[0];
            [email protected];
        }
        my $token = join "", @num;
        return ($token, "num_hex");
    }
    while( @buf > 0 && &is_digit($buf[0]) ){
        [email protected], $buf[0];
        [email protected];
    }
    # if is not .
    if( @buf == 0 || @buf > 0 && $buf[0] ne "." ){
        my $token = join "", @num;
        return ($token, "num_int");
    }
    # handle .
    [email protected], $buf[0];
    [email protected];
    while( @buf > 0 && &is_digit($buf[0]) ){
        [email protected], $buf[0];
        [email protected];
    }
    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下完成編碼和測試,最後計算出正確答案的時候,悄悄的,北京的房價又漲了幾千,人民幣又貶值了幾塊,太陽東昇西落了幾次。

       微風吹開我的長髮,我拿著寫著結果的紙片,輕盈的走向我的小夥伴,它,它,它,居然不理我了!不理我了!居然不理我了!!!(旁觀者眼中:主角得不到小夥伴的認可,吐血身亡。)

       畫外音:天才都短命。珍愛生命,遠離天才。

相關推薦

從0到1實現一個簡單計算器

### 前言 學習程式語言最重要的就是實踐。很多小夥伴在學習完程式語言後,一直停留在基礎階段,只懂一大堆理論知識,而不懂得實踐。那麼,今天我們一起來動手做一個小計算器,回顧下學習過的知識,同時這也是很多大學計算機專業的期末作業,應該嘗試動手並完全理解它,學廢了記得點贊收藏加關注哦。 ## 開始動手 **

HTML、CSS、JavaScript 實現一個簡單計算器

計算器效果圖: 程式碼如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>計算器</title> </head

Android下實現一個簡單計算器原始碼

下面的內容是關於Android下實現一個簡單的計算器的內容。 import android.app.Activity; import android.os.Bundle;import android.view.View;import android.widget.Button;import android.w

【 C 】轉移表(理論與實踐)(實現一個簡單計算器

首先借用《C 與指標》上對於轉移表的解釋,然後我們自己程式設計序操作下: 轉移表最好用個例子來解釋。下面的程式碼段取自一個程式,它用於實現一個袖珍式計算器。程式的其他部分已經讀入兩個數(op1和op2

簡單工廠模式---一個簡單計算器實現

1.面向物件程式設計    所有的程式設計初學者都會有這樣的問題,就是碰到問題就直覺地用計算機能夠理解的邏輯來描述和表達待解決的問題及具體的求解過程。這其實就是用計算機的方式去思考,是一種面向過程的開發方式,比如計算器這個程式,先要求輸入兩個數和運算子號,然後根據運算子號判

一個基於python3+PyQt5實現簡單計算器程式

【開發平臺】ubuntu14 + python3 + PyQt5 【執行介面】 【原始碼】 # -*- coding: utf-8 -*- ''' Description: A simple calculater based on PyQt5 Author: wa

Html+javascript實現一個簡單計算器,可繼續計算

先展示出計算器的大致樣子 <body> <div id="main"> <table frame="box" rules="all"> <tr> <td colspan="5" ><input

PHP實現一個簡單計算器(在html頁面和php檔案之間傳值)

先一個小案例簡單說明一下,從html頁面提交的資料如何傳給另一個php檔案。 先是一個簡單的html頁面: <html> <body> <form action="test1.php" method="post">

實現一個簡單計算器

故事:         幾個星期前,我和我的小夥伴一起去華聯超市買東西。它想去買菜,我想去買零食,我們說好了不買其他東西。不幸的是,我們高高興興的從超市的一樓逛到三樓的時候,小車已經不能裝下更多的東西了。我們互噴了一句,很不高興的付了賬,很高興的把東西搬了回去。         當回去分贓的時候,我發現一個

QT實現一個簡單計算器

最近幾天在學習QT,在瞭解了訊號和槽機制、佈局管理、還有一些控制元件的使用後,便試著寫了一個計算器,帶自定義的快捷鍵,剪貼簿(複製和貼上)的操作,有 選單欄->選擇 裡的功能還未實現,等啥時候有時間把它補上吧。還有許多不足和需要修改的地方,希望各位大牛能幫忙指出其中的

實現一個簡單的lazyman

實現 blog cti name init timeout bin bsp stack function lazyman(name) { return new lazyman.fn.init(name); } lazyman.fn = lazyman.proto

用java實現一個簡單的單用戶登陸功能的思路

get 單用戶 這樣的 簡單的 lock ref 數據庫 清除 一個 引用 所謂“單用戶單賬戶登錄”是指:在同一系統中,一個用戶名不能在兩個地方同時登錄。 我們參照 QQ 實現效果:當某賬號在 A 處登錄後,在未退出的情況下,如果再到 B 處登錄,那麽,系統會擠下 A 處

【Java】Swing+IO流實現一個簡單的文件加密程序

als oncommand override fault 源文件 abs directory imp select EncrytService package com.my.service; import java.io.File; import java

【Java】Swing+IO流實現一個簡單的文件加密程序(較完整版)

move 初始 baidu images 文件選擇器 while login 一個 ktr 留著參考 beans package com.my.bean; import java.io.Serializable; public class

Linux中實現一個簡單的進度條【轉】

做的 會有 發現 文件 rsquo 實時 時間 改進 常見 轉自:http://blog.csdn.net/yuehailin/article/details/53999288 說起進度條,其實大家常常見到,比如說你在下載視頻或文件的時候,提示你當前下載進度的就是我們今天

用 C# 實現一個簡單的 Rest Service 供外部調用

message [] operation rem adk www span method title 用 C# 實現一個簡單的 Restful Service 供外部調用,大體總結為4點: The service contract (the methods it o

jQuery實現一個簡單的購物車功能

名稱 展示 -1 set margin for button ans return 最近由於工作需要的原因,開始系統學習jQuery的知識,然後跟著一個視頻教程做了一個購物車的功能,現總結如下。 第一步:準備HTML頁面,代碼如下: <!DOCTYPE html P

實現一個簡單的ConnectionPool

方法 == span sql 需要 動手 cti 修改 使用 看了一圈, 沒看到稍微好用的ConnectionPool, 除了一個aiomysql, 但是這個是異步的, 我暫時沒有用到這麽高版本的Python, 所以就動手造一個輪子. 原理比較簡單, 先造一個線程安全的集

實現一個簡單的虛擬demo算法

child mov 箭頭 內存 架構模式 ren 操作 inpu 設置 假如現在你需要寫一個像下面一樣的表格的應用程序,這個表格可以根據不同的字段進行升序或者降序的展示。 這個應用程序看起來很簡單,你可以想出好幾種不同的方式來寫。最容易想到的可能是,在你的 JavaScr

自己動手實現一個簡單的JSON解析器

pair bool 優點 輕量 結束 pan isdigit 復雜 false 1. 背景 JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。相對於另一種數據交換格式 XML,JSON 有著諸多優點。比如易讀性更好,占用空間更少等