日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

太優雅了!Rust 200 行代碼實現表達式解析

來源: 責編: 時間:2024-04-30 08:43:30 281觀看
導讀表達式解析、計算是一種基本和常見的任務,例如最常見的算術表達式,計算的方法有很多,比如逆波蘭表達式、LL、LR 算法等等。這一次介紹一種最簡單的、容易理解的基于運算符優先級的算法來完成這個任務。基于運算符優先級

表達式解析、計算是一種基本和常見的任務,例如最常見的算術表達式,計算的方法有很多,比如逆波蘭表達式、LL、LR 算法等等。Cmm28資訊網——每日最新資訊28at.com

這一次介紹一種最簡單的、容易理解的基于運算符優先級的算法來完成這個任務。Cmm28資訊網——每日最新資訊28at.com

基于運算符優先級的算法叫做 Precedence Climbing,它本質上是一種遞歸下降解析表達式的方法,通過遞歸地處理運算符和操作數來解析表達式,并根據運算符的優先級和結合性來確定表達式的計算順序。Cmm28資訊網——每日最新資訊28at.com

這種算法的核心思想是利用運算符的優先級進行“爬升”(Climbing),以決定表達式的結構和計算順序。Cmm28資訊網——每日最新資訊28at.com

首先我們做一些約束,由于運算符眾多,我們可以支持幾種最常用的:Cmm28資訊網——每日最新資訊28at.com

  • + 加
  • - 減
  • * 乘
  • / 除
  • ^ 冪

并且我們知道,冪運算的優先級是最高的,其次是 * 和 /,優先級最低的是 + 和 -。所以約定其運算符的優先級分別為 3(^)、2(* /)、1(+ -)Cmm28資訊網——每日最新資訊28at.com

2 + 3 ^ 2 * 3 + 4|---------------|   : prec 1    |-------|       : prec 2    |---|           : prec 3

約定優先級的主要作用是在計算的時候,需要根據優先級來確定計算的順序。Cmm28資訊網——每日最新資訊28at.com

確定了優先級的問題,第二個問題是結合性,運算符的結合性其實也是確定的,例如加法是左結合的,這意味著 2 + 3 + 4 等價于 (2 + 3) + 4,而冪運算是右結合的,這意味著 2 ^ 3 ^ 4 實際上等價于 2 ^ (3 ^ 4)。Cmm28資訊網——每日最新資訊28at.com

最后還需要注意一個問題,那就是子表達式,也就是用括號包裹的部分,這部分實際上是需要單獨進行計算的,并且比運算符的優先級更高。Cmm28資訊網——每日最新資訊28at.com

其實也很容易理解,比如 2 * (3 + 5) * 7,盡管 * 的優先級比 + 高,但是需要先計算括號內的部分。Cmm28資訊網——每日最新資訊28at.com

確定了這些需求,我們再來看如何用 Rust 代碼來進行實現。Cmm28資訊網——每日最新資訊28at.com

首先我們需要將表達式進行解析,也就是詞法分析的階段,將一個表達式解析為不同的 Token,下面是約定的幾種 Token:Cmm28資訊網——每日最新資訊28at.com

// Token 表示,數字、運算符號、括號#[derive(Debug, Clone, Copy)]enum Token {    Number(i32),    Plus,       // 加    Minus,      // 減    Multiply,   // 乘    Divide,     // 除    Power,      // 冪    LeftParen,  // 左括號    RightParen, // 右括號}

然后定義了一個 Tokenizer 結構體,主要是利用 Peekable 接口將表達式解析為不同的 Token:Cmm28資訊網——每日最新資訊28at.com

// 將一個算術表達式解析成連續的 Token// 并通過 Iterator 返回,也可以通過 Peekable 接口獲取struct Tokenizer<'a> {    tokens: Peekable<Chars<'a>>,}

然后自定義實現了一個 Iterator,讓解析后的 Token 可以通過迭代器進行返回。Cmm28資訊網——每日最新資訊28at.com

impl<'a> Iterator for Tokenizer<'a> {    type Item = Token;    fn next(&mut self) -> Option<Self::Item> {        // 消除前面的空格        self.consume_whitespace();        // 解析當前位置的 Token 類型        match self.tokens.peek() {            Some(c) if c.is_numeric() => self.scan_number(),            Some(_) => self.scan_operator(),            None => return None,        }    }}

假如我們的表達式是 2 + 3 ^ 2 * 3 + 4,實際上解析后的 Token 就是:Cmm28資訊網——每日最新資訊28at.com

Token::Number(2)Token::PlusToken::Number(3)Token::PowerToken::Number(2)Token::MultiplyToken::Number(3)Token::PlusToken::Number(4)

拿到 Token 之后,進入到了語法分析的階段,需要根據每個表達式的含義,以及其優先級,計算對應的結果。Cmm28資訊網——每日最新資訊28at.com

首先定義一個方法,計算單個 Token 以及子表達式,這只存在兩種情況,分別是 Number 這個 Token,以及帶括號的子表達式。Cmm28資訊網——每日最新資訊28at.com

fn compute_atom(&mut self) -> Result<i32> {        match self.iter.peek() {            // 如果是數字的話,直接返回            Some(Token::Number(n)) => {                let val = *n;                self.iter.next();                return Ok(val);            }            // 如果是左括號的話,遞歸計算括號內的值            Some(Token::LeftParen) => {                self.iter.next();                let result = self.compute_expr(1)?;                match self.iter.next() {                    Some(Token::RightParen) => (),                    _ => return Err(ExprError::Parse("Unexpected character".into())),                }                return Ok(result);            }            _ => {                return Err(ExprError::Parse(                    "Expecting a number or left parenthesis".into(),                ))            }        }    }

這里其實比較好理解,如果是 Number 直接返回,如果是子表達式,則重新調用計算表達式的方法進行計算。Cmm28資訊網——每日最新資訊28at.com

然后是另一個核心的方法計算表達式:Cmm28資訊網——每日最新資訊28at.com

fn compute_expr(&mut self, min_prec: i32) -> Result<i32> {    // 計算第一個 Token    let mut atom_lhs = self.compute_atom()?;        loop {        let cur_token = self.iter.peek();        if cur_token.is_none() {            break;        }        let token = *cur_token.unwrap();        // 1. Token 一定是運算符        // 2. Token 的優先級必須大于等于 min_prec        if !token.is_operator() || token.precedence() < min_prec {            break;        }        let mut next_prec = token.precedence();        if token.assoc() == ASSOC_LEFT {            next_prec += 1;        }        self.iter.next();        // 遞歸計算右邊的表達式        let atom_rhs = self.compute_expr(next_prec)?;                // 得到了兩邊的值,進行計算        match token.compute(atom_lhs, atom_rhs) {            Some(res) => atom_lhs = res,            None => return Err(ExprError::Parse("Unexpected expr".into())),        }    }    Ok(atom_lhs)}

這個方法中核心的邏輯可以分幾個步驟來理解:Cmm28資訊網——每日最新資訊28at.com

一是使用了 min_prec 參數控制當前層級的優先級,如果表達式的優先級小于 min_prec 則直接跳出循環,返回當前的值。Cmm28資訊網——每日最新資訊28at.com

比如 2 * 3 + 4,* 會先解析到,然后 + 運算符的優先級明顯比 * 更低,會直接返回當前值 3。Cmm28資訊網——每日最新資訊28at.com

二是如果運算符的結合性是左邊的話,則下一次迭代的 min_prec 需要遞增。Cmm28資訊網——每日最新資訊28at.com

比如表達式是 2 * 3 * 4,解析到第二個 * 的時候,* 的優先級本來是 2,但它是左結合的,所以此時 min_prec 是 3,會直接跳出循環,所以實際上會先計算 2 * 3。Cmm28資訊網——每日最新資訊28at.com

最后是得到了運算符兩邊的值,就可以進行計算了,這里是根據運算符的實際含義來進行的:Cmm28資訊網——每日最新資訊28at.com

// 根據當前運算符進行計算fn compute(&self, l: i32, r: i32) -> Option<i32> {    match self {        Token::Plus => Some(l + r),        Token::Minus => Some(l - r),        Token::Multiply => Some(l * r),        Token::Divide => Some(l / r),        Token::Power => Some(l.pow(r as u32)),        _ => None,    }}

這就是根據運算符優先級來進行表達式計算的整體流程,這個算法看起來還是非常簡潔優雅的,非常巧妙的利用優先級來解決運算的順序和結合等問題。Cmm28資訊網——每日最新資訊28at.com

完整的代碼也只有 200 多行,比較適合用來練手,通過這個項目,可以學習到:Cmm28資訊網——每日最新資訊28at.com

  • 一個優雅、簡潔的表達式計算的算法
  • 解決類似寫一個計算器的面試問題
  • Rust 基礎數據類型、枚舉、結構體基本用法
  • 函數、遞歸
  • match 表達式
  • 自定義 Result 錯誤處理
  • 迭代器的常見用法 next、peekable 等
  • 自定義迭代器
  • Option 使用

本文鏈接:http://www.www897cc.com/showinfo-26-86686-0.html太優雅了!Rust 200 行代碼實現表達式解析

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 深入剖析:如何使用Pulsar和Arthas高效排查消息隊列延遲問題

下一篇: C#實戰:圖像清晰度增強介紹和案例實踐

標簽:
  • 熱門焦點
  • 印度登月最關鍵一步!月船三號今晚進入環月軌道

    8月5日消息,據印度官方消息,月船三號將于北京時間今晚21時30分左右開始近月制動進入環月軌道。這是該探測器能夠成功的最關鍵步驟之一,如果成功將開始圍
  • JavaScript 混淆及反混淆代碼工具

    介紹在我們開始學習反混淆之前,我們首先要了解一下代碼混淆。如果不了解代碼是如何混淆的,我們可能無法成功對代碼進行反混淆,尤其是使用自定義混淆器對其進行混淆時。什么是混
  • 如何正確使用:Has和:Nth-Last-Child

    我們可以用CSS檢查,以了解一組元素的數量是否小于或等于一個數字。例如,一個擁有三個或更多子項的grid。你可能會想,為什么需要這樣做呢?在某些情況下,一個組件或一個布局可能會
  • 每天一道面試題-CPU偽共享

    前言:了不起:又到了每天一到面試題的時候了!學弟,最近學習的怎么樣啊 了不起學弟:最近學習的還不錯,每天都在學習,每天都在進步! 了不起:那你最近學習的什么呢? 了不起學弟:最近在學習C
  • 雅柏威士忌多款單品價格大跌,泥煤頂流也不香了?

    來源 | 烈酒商業觀察編 | 肖海林今年以來,威士忌市場開始出現了降溫跡象,越來越多不斷暴漲的網紅威士忌也開始悄然回歸市場理性。近日,LVMH集團旗下蘇格蘭威士忌品牌雅柏(Ardbeg
  • 2023年,我眼中的字節跳動

    此時此刻(2023年7月),字節跳動從未上市,也從未公布過任何官方的上市計劃;但是這并不妨礙它成為中國最受關注的互聯網公司之一。從2016-17年的抖音強勢崛起,到2018年的&ldquo;頭騰
  • 電視息屏休眠仍有網絡上傳 愛奇藝被質疑“薅消費者羊毛”

    記者丨寧曉敏 見習生丨汗青出品丨鰲頭財經(theSankei) 前不久,愛奇藝發布了一份亮眼的一季報,不僅營收和會員營收創造歷史最佳表現,其運營利潤也連續6個月實現增長。自去年年初
  • 新電商三兄弟,“抖快紅”成團!

    來源:價值研究所作 者:Hernanderz 隨著內容電商的概念興起,抖音、快手、小紅書組成的&ldquo;新電商三兄弟&rdquo;成為業內一股不可忽視的勢力,給阿里、京東、拼多多帶去了巨大壓
  • 機構稱Q2全球智能手機出貨量同比下滑11% 蘋果份額依舊第2

    7月20日消息,據外媒報道,研究機構的報告顯示,由于需求下滑,今年二季度全球智能手機的出貨量,同比下滑了11%,三星、蘋果等主要廠商的銷量,較去年同期均有下
Top 主站蜘蛛池模板: 大厂| 凌海市| 苏州市| 安多县| 金华市| 开原市| 南涧| 信宜市| 遂昌县| 宝山区| 余庆县| 铁力市| 册亨县| 阳原县| 郯城县| 平江县| 富蕴县| 饶河县| 嘉祥县| 湖口县| 苍南县| 石屏县| 石嘴山市| 绍兴县| 页游| 高碑店市| 格尔木市| 商河县| 盖州市| 宕昌县| 贵阳市| 平邑县| 深水埗区| 康乐县| 沛县| 章丘市| 红河县| 万荣县| 文成县| 红安县| 循化|