成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java 應(yīng)用:自制高精度計(jì)算器(2)

yy736044583 / 1078人閱讀

摘要:而對(duì)于前綴表達(dá)式和后綴表達(dá)式的計(jì)算,則十分的簡(jiǎn)單。由上一篇文章可知,我們目前的類所表示的,就是中綴表達(dá)式,所以我們需要提供算法,將中綴表達(dá)式轉(zhuǎn)換為前綴表達(dá)式或者后綴表達(dá)式,從而方便我們計(jì)算表達(dá)式的值。

上一篇 文章講了如何通過正則來將輸入的表達(dá)式解析為多個(gè) Token,而這篇文章的核心在于如何對(duì) 表達(dá)式求值。
我們輸入的表達(dá)式,即我們通常見到的表達(dá)式,都是中綴表達(dá)式 —— 中綴的含義是,在表達(dá)式中,運(yùn)算符是放中間的,比如 (1 + 2) * 3,運(yùn)算符都是在數(shù)的中間。然而在計(jì)算機(jī)的世界里,還存在著前綴表達(dá)式和后綴表達(dá)式 —— 由名字也很容易知道,前綴表達(dá)式是將運(yùn)算符放在數(shù)之前,后綴表達(dá)式是將運(yùn)算符放到數(shù)之后。

表達(dá)式 形式
中綴 1 + (3 - 2) * 4 / 5
前綴 + 1 / * - 3 2 4 5
后綴 1 3 2 - 4 * 5 / +

中綴表達(dá)式的劣勢(shì)在于,一旦表達(dá)式復(fù)雜化,比如多層括號(hào)嵌套同時(shí)還要注意運(yùn)算符的優(yōu)先級(jí),那么要編寫計(jì)算中綴表達(dá)式的值的代碼也十分的復(fù)雜。而對(duì)于前綴表達(dá)式和后綴表達(dá)式的計(jì)算,則十分的簡(jiǎn)單。

以后綴表達(dá)式為例:

從左往右掃描表達(dá)式,如果遇到數(shù),那么將數(shù)入棧

如果遇到運(yùn)算符,那么從棧中依次彈出兩個(gè)數(shù) n1 和 n2,使用該運(yùn)算符對(duì)這兩個(gè)數(shù)進(jìn)行運(yùn)算(n2 op n1),將獲得的結(jié)果數(shù)入棧

重復(fù) 1 和 2 直到表達(dá)式掃描結(jié)束,那么棧中最后剩余的數(shù)便是表達(dá)式的值。

比如上面的例子,1 + (3 - 2) * 4 / 5 = 1.8,對(duì)于后綴表達(dá)式 1 3 2 - 4 * 5 / +

當(dāng)前 Token 操作 棧(棧頂在左邊)
1 遇到數(shù)直接入棧 1
2 遇到數(shù)直接入棧 3 1
3 遇到數(shù)直接入棧 2 3 1
- n1 = 2, n2 = 3;n2 op n1 = 3 – 2 = 1,并將 1 入棧 1 1
4 遇到數(shù)直接入棧 4 1 1
* n1 = 4, n2 = 1;n2 op n1 = 4 * 1 = 4,并將 4 入棧 4 1
5 遇到數(shù)直接入棧 5 4 1
/ n1 = 5, n2 = 4;n2 op n1 = 4 / 5 = 0.8,并將 0.8 入棧 0.8 1
+ n1 = 0.8, n2 = 1;n2 op n1 = 1 + 0.8 = 1.8,并將 1.8 入棧 1.8

所以可見計(jì)算后綴表達(dá)式非常容易編碼。

由 上一篇 文章可知,我們目前的 Expression 類所表示的,就是中綴表達(dá)式,所以我們需要提供算法,將中綴表達(dá)式轉(zhuǎn)換為前綴表達(dá)式或者后綴表達(dá)式,從而方便我們計(jì)算表達(dá)式的值。當(dāng)然,算法的流程,我們的計(jì)算機(jī)先輩們?cè)缇拖氤鰜砹?,而我們只需要做出?shí)現(xiàn)即可。

同樣以后綴表達(dá)式為例,中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式的算法流程如下:

初始化運(yùn)算符棧 S 和用來保存中間結(jié)果的列表 L

從左往右掃描中綴表達(dá)式:

遇到數(shù)時(shí),直接將其加入到 L

遇到運(yùn)算符 op 時(shí)

2.1 如果 S 為空,那么直接將 op 入棧 S
2.2 如果 S 不空,并且 S 棧頂為左括號(hào) "(",那么將 op 入棧 S
2.3 如果 S 不空,此時(shí) S 棧頂為運(yùn)算符,如果 op 的優(yōu)先級(jí)大于 S 棧頂元素的優(yōu)先級(jí),那么將該運(yùn)算符入棧 S
2.4 否則(即 op 的優(yōu)先級(jí)小于 S 棧頂元素的優(yōu)先級(jí)),將 S 的棧頂元素彈出,并將該元素加入 L;然后轉(zhuǎn)到 2.1 繼續(xù)判斷并比較。

遇到括號(hào)時(shí)

3.1 如果是左括號(hào) "(",直接將該左括號(hào)入棧 S
3.2 如果是右括號(hào) ")",依次彈出 S 中的運(yùn)算符,直到遇到一個(gè)左括號(hào)為止,然后將這一對(duì)括號(hào)都丟棄

將 S 中的剩余運(yùn)算符依次彈出并加入 L

此時(shí) L 中的所有的 Token 按順序即為后綴表達(dá)式

(關(guān)于中綴、前綴、后綴表達(dá)式轉(zhuǎn)換算法更詳細(xì)的內(nèi)容和例子,可以參考 前綴、中綴、后綴表達(dá)式 這篇文章,本文所寫的算法流程也是參考這篇文章而來。)

根據(jù)上面的算法,我們就不難在目前的 Expression 基礎(chǔ)上,寫出將中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式的算法,我們將這個(gè)方法命名為 toPostfixExpr()

/**
 * 獲得該表達(dá)式的后綴形式
 *
 * @return 后綴表達(dá)式
 */
public Expression toPostfixExpr() {
    ArrayDeque S = new ArrayDeque<>(); // 運(yùn)算符棧
    ArrayList L = new ArrayList<>();   // 保存中間結(jié)果的列表

    for (Token token : tokens) {
        switch (token.getType()) {
            case NUMBER:
                L.add(token);
                break;

            case OPERATOR:
                Operator op = (Operator) token;
                boolean back = true;

                while (back) {
                    back = false;

                    if (S.isEmpty()) { // 運(yùn)算符棧為空
                        S.push(op);

                    } else {  // 運(yùn)算符棧不為空
                        Token top = S.peek();

                        // 運(yùn)算符棧棧頂為 "("
                        if (top.isBracket() && ((Bracket) top).isLeft()) {
                            S.push(op);

                        // op 的優(yōu)先級(jí)大于運(yùn)算符棧棧頂元素的優(yōu)先級(jí)
                        } else if (op.isHigherThan((Operator) top)) {
                            S.push(token);

                        } else { // op 的優(yōu)先級(jí)小于運(yùn)算符棧棧頂元素的優(yōu)先級(jí)
                            L.add(S.pop());
                            back = true; // 回到 while
                        }
                    }
                }
                break;

            case BRACKET:
                if (((Bracket) token).isLeft()) {
                    S.push(token);

                } else {
                    for (Token t = S.pop();
                            !t.isBracket(); t = S.pop()) {
                        L.add(t);
                    }
                }
                break;
        }
    }

    while (!S.isEmpty()) {
        L.add(S.pop());
    }

    return new Expression(L, true); // true 表示該表達(dá)式為后綴表達(dá)式
}

此時(shí)我們往 Expression 中添加了一個(gè) boolean 字段 postfix,用來標(biāo)識(shí)該表達(dá)式是否為后綴表達(dá)式,postfix 默認(rèn)為 false,如果為 true 則表明該表達(dá)式是后綴表達(dá)式。

public class Expression {
    ...

    private final List tokens; // 該表達(dá)式中的所有 Token
    private final boolean postfix;    // 該表達(dá)式是否為后綴表達(dá)式的標(biāo)識(shí)

    public Expression(List tokens, boolean postfix) {
        this.tokens = tokens;
        this.postfix = postfix;
    }

    /**
     * 該表達(dá)式是否為后綴表達(dá)式
     *
     * @return 如果該表達(dá)式為后綴表達(dá)式返回 true,否則返回 false
     */
    public boolean isPostfix() {
        return postfix;
    }

    ...
}

然后,根據(jù)后綴表達(dá)式,我們也很容易寫出計(jì)算表達(dá)式值的方法,我們將方法命名為 calculate()

/**
 * 通過后綴表達(dá)式計(jì)算表達(dá)式的值
 *
 * @return 表達(dá)式的值
 */
public Num calculate() {
    if (!isPostfix()) {
        throw new RuntimeException("請(qǐng)先將表達(dá)式轉(zhuǎn)為后綴表達(dá)式再計(jì)算");
    }

    ArrayDeque stack = new ArrayDeque<>();

    for (Token token : tokens) {

        if (token.isNumber()) {
            stack.push(token);

        } else {
            Num n1 = (Num) stack.pop();
            Num n2 = (Num) stack.pop();
            Operator op = (Operator) token;

            Num result = n2.operate(op, n1);
            stack.push(result);
        }

    }

    if (stack.size() != 1) { // 棧中最后剩下的不止一個(gè)數(shù),說明表達(dá)式有問題
        throw new RuntimeException("錯(cuò)誤的表達(dá)式");
    }

    return (Num) stack.pop();
}

此時(shí)我們?cè)?Num 類中定義了一個(gè) operate 方法,用來根據(jù)運(yùn)算符對(duì)兩個(gè)數(shù)進(jìn)行運(yùn)算:

public static final RoundingMode MODE
        = RoundingMode.HALF_UP;  // 默認(rèn)對(duì)末尾小數(shù)采用 四舍五入

public static final MathContext MATH_CONTEXT
        = new MathContext(6, MODE); // 無限循環(huán)時(shí)保留6位有效數(shù)字,末位四舍五入

public Num operate(Operator op, Num other) {
    BigDecimal result = null;

    switch (op.value()) {
        case "+":
            result = value.add(other.value);
            break;
        case "-":
            result = value.subtract(other.value);
            break;
        case "*":
            result = value.multiply(other.value);
            break;
        case "/":
            result = value.divide(other.value, MATH_CONTEXT);
            break;
    }

    if (result == null) {
        throw new RuntimeException(String.format(
                "operate 方法出錯(cuò):%s %s %s", value, op.text(), other.value));
    }

    return new Num(result);
}

目前來看一切都很完美 —— 但是我們忽略了一種情況,那就是輸入負(fù)數(shù)的情況。而此時(shí)存在以下兩種情況:

輸入的表達(dá)式開頭是負(fù)數(shù),比如 -1 + 2 * 3,這種情況容易解決,我們只需要在開頭補(bǔ)上一個(gè) 0,便能適應(yīng)現(xiàn)在的程序,比如該表達(dá)式補(bǔ)上 0 后就成為 0 - 1 + 2 * 3,結(jié)果一致

另一種情況就是表達(dá)式中出現(xiàn)負(fù)數(shù)了 —— 此時(shí)我們需要對(duì)負(fù)數(shù)進(jìn)行特殊的標(biāo)識(shí),比如按照一般情況將負(fù)數(shù)使用括號(hào)()包圍。所以我們需要補(bǔ)充我們的正則表達(dá)式,讓其可以匹配類似于 (-12)(-100.100) 這樣的 Token。

修改之后的 Expression(-(d*.d+|d+)) 用來匹配負(fù)數(shù)):

public class Expression {

    private static final String REG_EXPR = "s*(((-(d*.d+|d+)))|(d*.d+|d+)|(+|-|*|/)|((|))|([A-Za-z]+(.*)))s*";
    private static final Pattern PATTERN = Pattern.compile(REG_EXPR);

    ...

    private static Token getToken(Matcher matcher) {
        // matcher.group(0) 匹配整個(gè)正則,matcher.group(1) 匹配第一個(gè)括號(hào)
        String m = matcher.group(1);

        if (m != null) {
            if (matcher.group(2) != null) { // 負(fù)數(shù)
                // matcher.group(3) 提取形如 (-1.2) 中的 1.2
                return new Num("-" + matcher.group(3));

            } else if (matcher.group(4) != null) { // 正數(shù)
                return new Num(matcher.group(4));

            } else if (matcher.group(5) != null) { // 運(yùn)算符
                return new Operator(matcher.group(5).charAt(0));

            } else if (matcher.group(6) != null) { // 括號(hào)
                return new Bracket(matcher.group(6).charAt(0));

            } else if (matcher.group(7) != null) { // 函數(shù)
                Function function = new Function(matcher.group(7));
                Num num = function.getResult(); // 直接計(jì)算出函數(shù)的值作為 Token

                return num;
            }
        }

        throw new RuntimeException("getToken"); // 正則無誤的情況下不會(huì)發(fā)生
    }

    ...

}

現(xiàn)在,讓我們來寫一個(gè)主類,從命令行獲得輸入并且計(jì)算輸入的表達(dá)式的值。我們將主類命名為 Launcher

public class Launcher {

    public static void main(String[] args) throws Exception {

        System.out.println("歡迎使用你的計(jì)算器(輸入 e(xit) 退出)");

        try (Reader in = new InputStreamReader(System.in);
                BufferedReader reader = new BufferedReader(in)) {

            String line;
            while (true) {
                System.out.print("> ");
                line = reader.readLine();

                if (null == line
                        || "e".equalsIgnoreCase(line)
                        || "exit".equalsIgnoreCase(line)) {
                    break;
                } else if (line.isEmpty()) {
                    continue;
                }

                try {
                    Expression expr = Expression.of(line);
                    Expression postfixExpr = expr.toPostfixExpr();
                    Num result = postfixExpr.calculate();

                    System.out.println(result);

                } catch (ArithmeticException ex) {
                    System.out.println("運(yùn)算錯(cuò)誤:" + ex.getMessage());
                } catch (RuntimeException ex) {
                    System.out.println("運(yùn)行錯(cuò)誤:" + ex.getMessage());
                    // ex.printStackTrace(System.err);
                }

            }
        }
    }
}

可以看到,我們已經(jīng)可以成功的解析表達(dá)式,并且計(jì)算表達(dá)式的值(完整的源碼在 GitHub)。

當(dāng)然,我們總不能每次運(yùn)行都用 Maven 來運(yùn)行項(xiàng)目,所以我們把項(xiàng)目打包成 jar,然后寫個(gè)腳本來執(zhí)行這個(gè) jar,最后將腳本加入到 PATH 中,那么便可以在命令行下直接調(diào)用。
我們將打包的 jar 命名為 mcalc.jar (打包的配置可參考 pom.xml),然后寫個(gè)簡(jiǎn)單的腳本。比如,在 Windows 上,寫個(gè) mcalc.bat:

@echo off
:: %~dp0 表示當(dāng)前批處理文件所在目錄的路徑
set DIR_PATH=%~dp0
java -jar %DIR_PATH%mcalc.jar

然后將 mcalc.bat 和 mcalc.jar 放到同一個(gè)文件夾下,然后將這個(gè)文件夾的路徑加入到 PATH。此時(shí)在命令行中直接輸入 mcalc,便可以進(jìn)入程序:

參考:

http://blog.csdn.net/antineut...

http://blog.csdn.net/yu757371...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70273.html

相關(guān)文章

  • Java 應(yīng)用自制精度計(jì)算器(1)

    摘要:一直以來,我的計(jì)算器都是的之后偶爾也用。因?yàn)槲覀円褂酶呔葦?shù)來代替浮點(diǎn)數(shù),所以的真正實(shí)現(xiàn),交給了。我們通常使用計(jì)算器的函數(shù)都是形如這樣的形式,所以我們定義函數(shù)的正則為表示參數(shù)可以有也可以沒有,即存在這樣的函數(shù)。 一直以來,我的計(jì)算器都是 Python 的 REPL(Java8 之后偶爾也用 jjs (Nashorn))。但是這些 REPL 的問題在于,在涉及到小數(shù)時(shí),它們使用的是浮點(diǎn)...

    Muninn 評(píng)論0 收藏0
  • 私有云部署-UCloudStack私有云部署之虛擬機(jī)

    摘要:虛擬網(wǎng)卡與虛擬機(jī)的生命周期一致,無法進(jìn)行分離,虛擬機(jī)被銷毀時(shí),虛擬網(wǎng)卡即被銷毀。每塊虛擬網(wǎng)卡支持綁定一個(gè)安全組,提供網(wǎng)卡級(jí)別安全控制。平臺(tái)默認(rèn)提供塊虛擬網(wǎng)卡,若業(yè)務(wù)有塊以上網(wǎng)卡需求可通過綁定彈性網(wǎng)卡,為虛擬機(jī)提供多網(wǎng)絡(luò)服務(wù)。虛擬機(jī)是 UCloudStack 云平臺(tái)的核心服務(wù),提供可隨時(shí)擴(kuò)展的計(jì)算能力服務(wù),包括 CPU 、內(nèi)存、操作系統(tǒng)等最基礎(chǔ)的計(jì)算組件,并與網(wǎng)絡(luò)、磁盤等服務(wù)結(jié)合提供完整的計(jì)算...

    ernest.wang 評(píng)論0 收藏0
  • Java爬蟲之利用Jsoup自制簡(jiǎn)單的搜索引擎

    摘要:的官方網(wǎng)址為,其使用手冊(cè)網(wǎng)址為本次分享將實(shí)現(xiàn)的功能為利用爬取某個(gè)搜索詞語暫僅限英文的百度百科的介紹部分,具體的功能介紹可以參考博客爬蟲自制簡(jiǎn)單的搜索引擎。 ??Jsoup 是一款Java 的HTML解析器,可直接解析某個(gè)URL地址、HTML文本內(nèi)容。它提供了一套非常省力的API,可通過DOM,CSS以及類似于jQuery的操作方法來取出和操作數(shù)據(jù)。Jsoup的官方網(wǎng)址為: https:...

    GHOST_349178 評(píng)論0 收藏0
  • Java跨平臺(tái)?慎用這些有平臺(tái)差異性的方法

    摘要:坑一慎用方法在類中,有一個(gè)方法是,返回的是一個(gè)數(shù)組,該數(shù)組包含了所包含的方法??佣饔镁€程優(yōu)先級(jí)做并發(fā)處理線程中有屬性,表示線程的優(yōu)先級(jí),默認(rèn)值為,取值區(qū)間為。顯然,運(yùn)行時(shí)環(huán)境是因操作系統(tǒng)而異的。 本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處。 我們都知道Java是跨平臺(tái)的,一次編譯,到處運(yùn)行,本質(zhì)上依賴于不同操作系統(tǒng)下有不同的JVM。到處運(yùn)行是做到了,但運(yùn)行結(jié)果呢?一樣的程序,在不同的JVM上跑的...

    hidogs 評(píng)論0 收藏0
  • 使用BigDecimal處理Java中的小數(shù)

    摘要:在中處理帶小數(shù)的數(shù)據(jù)時(shí),通常會(huì)碰到需要進(jìn)行對(duì)數(shù)據(jù)進(jìn)行四舍五入或者截取等操作。提供了一個(gè)的方法,很方便的幫助我們實(shí)現(xiàn)想要的操作。 在Java中處理帶小數(shù)的數(shù)據(jù)時(shí),通常會(huì)碰到需要進(jìn)行對(duì)數(shù)據(jù)進(jìn)行四舍五入或者截取等操作。BigDecimal提供了一個(gè)setScale()的方法,很方便的幫助我們實(shí)現(xiàn)想要的操作。 通常用到的是下面的方法 setScale(int newScale, in...

    用戶84 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<