• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Swift后缀表达式(逆波兰式)转换计算

武飞扬头像
搜狐技术产品小编2023
帮助1

学新通

 学新通 

本文字数:8396

预计阅读时间:21分钟

背景

最近在开发《挑战24点》的过程中遇到了一个问题,即,如何计算常用数学表达式的结果,比如,给定字符串8 - (6 4 / 2 - 1) * 2,怎么计算得到结果,并且得到计算的过程。

网上查资料发现,大部分都是类似系统计算器的处理,在遇到第二个运算符时,就把前一步的操作结果计算出来。这样的处理方式并不适用 于笔者想要解决的问题。

进一步搜索后发现,前缀表达式、中缀表达式、后缀表达式的概念,给定的字符串8 - (6 4 / 2 - 1) * 2属于中缀表达式,而想要计算机得出结果,可以转为前缀表达式或者后缀表达式,然后再对转换后的表达式进行计算。

这里采用中缀表达式转后缀表达式,然后计算后缀表达式得出结果,步骤如下。

Swift 中缀表达式转后缀表达式

什么是中缀表达式、后缀表达式?

首先理解中缀表达式和后缀表达式分别是什么?

  • 中缀表达式: 是常用的算术表示方法,操作符处于操作数的中间,比如 (a b),即中缀形式,故而称之为中缀表达式。(https://baike.百度.com/item/中缀表达式/2725244)

  • 后缀表达式: 运算符写在操作数之后,比如 (a, b, ),称之为后缀表达式,又名逆波兰式。(https://baike.百度.com/item/逆波兰式/128437?fromtitle=后缀表达式&fromid=6160580)

为什么要把中缀表达式转为后缀表达式?

为什么要将简单的中缀表达式转为后缀表达式呢?因为中缀表达式的简单对于计算机来说是非常复杂的,没有办法直接运算,而后缀表达式对于计算机而言是简单易懂的结构。所以计算常见的表达式时,要转为后缀表达式,然后运算。

怎么转?

然后来看下,如何把中缀表达式转为后缀表达式,这里建议先看一遍,理解后,在本子上按照原理尝试一遍,更能理解。原理:

  1. 由于 Swift 中没有栈的概念,所以采用数组的实现方式,用数组 append 模拟入栈,popLast 来模拟出栈。

  2. 声明两个数组,分别用于存储数字和运算符

  3. 从左到右遍历表达式,

    1. 运算符数组中最后一个为"("时,或者要放入的运算符为"(",则不需要比较优先级,直接把要放入的运算符放入运算符数组中

    2. 如果要放入的运算符的优先级不大于运算符数组中最后一个的优先级,则把运算符数组中的最后一个弹出放入到数字数组中,直到遇到优先级比它低的时停止或者遇到"("时停止。然后把运算符放入运算符数组中。

    3. 遇到" ",继续遍历下一个字符

    4. 遇到数字则放入数字数组中

    5. 遇到")",则把运算符数组中最后一个元素弹出,直到遇到"("时停止。

    6. 遇到运算符,则比较运算符的优先级,

  4. 遍历表达式完成后,如果运算符数组不为空,则把运算符数组中的元素倒序弹出,放入到数字数组中

  5. 最后返回数字数组,即所需要的后缀表达式的数组

流程如下:

学新通算术表达式转后缀表达式.png

假设现有一个表达式:8 - (6 4 / 2 - 1) * 2,按照上面的步骤实践如下:

  1.  
    // 初始化两个数组,上面为数字数组、下面为运算符数组
  2.  
    []
  3.  
    []
  4.  
     
  5.  
    // 下一个字符为"8",是数字,直接放入数字数组中
  6.  
    ["8"]
  7.  
    []
  8.  
     
  9.  
    // 下一个字符为"-",是运算符,运算符数组为空,故而不需要比较优先级,直接放入运算符数组中
  10.  
    ["8"]
  11.  
    ["-"]
  12.  
     
  13.  
    // 下一个字符为"(",是运算符,要放入的元素是"(",不需要比较优先级,"("直接放入运算符数组中
  14.  
    ["8"]
  15.  
    ["-""("]
  16.  
     
  17.  
    // 下一个字符为"6",是数字,放入数字数组中
  18.  
    ["8""6"]
  19.  
    ["-""("]
  20.  
     
  21.  
    // 下一个字符为" ",是运算符,运算符数组中最后一个元素为"(",不需要比较优先级," "直接放入运算符数组中
  22.  
    ["8""6"]
  23.  
    ["-""("" "]
  24.  
     
  25.  
    // 下一个字符为"4",是数字,放入数字数组中
  26.  
    ["8""6""4"]
  27.  
    ["-""("" "]
  28.  
     
  29.  
    // 下一个字符为"/",是运算符,"/"比运算符数组中最后一个元素" "的优先级高,故而"/"直接放入运算符数组中
  30.  
    ["8""6""4"]
  31.  
    ["-""("" ""/"]
  32.  
     
  33.  
    // 下一个字符为"2",是数字,放入数字数组中
  34.  
    ["8""6""4""2"]
  35.  
    ["-""("" ""/"]
  36.  
     
  37.  
    // 下一个字符为"-",是运算符,
  38.  
    // "-"比运算符数组中最后一个元素"/"的优先级低,故而"/"从运算符数组中弹出,添加到数字数组中。
  39.  
    // 再次比较"-"优先级不高于运算符数组中最后一个元素" ",故而" "从运算符数组中弹出,添加到数字数组中。
  40.  
    // 再次比较,运算符数组中最后一个元素为"(",故而不需要比较优先级,"-"放入运算符数组中
  41.  
    ["8""6""4""2""/"" "]
  42.  
    ["-""(""-"]
  43.  
     
  44.  
    // 下一个字符为"1",是数字,放入数字数组中
  45.  
    ["8""6""4""2""/"" ""1"]
  46.  
    ["-""(""-"]
  47.  
     
  48.  
    // 下一个字符为")",是运算符,把运算符数组中最后一个元素弹出,直到遇到"("时停止,且把"("从运算符数组中移出
  49.  
    ["8""6""4""2""/"" ""1""-"]
  50.  
    ["-"]
  51.  
     
  52.  
    // 下一个字符为"*",是运算符,"*"的优先级比运算符数组中最后一个元素"-"的优先级高,故而直接放入运算符数组中
  53.  
    ["8""6""4""2""/"" ""1""-"]
  54.  
    ["-""*"]
  55.  
     
  56.  
    // 最后,把运算符数组中的元素倒序放入数字数组中
  57.  
    ["8""6""4""2""/"" ""1""-""2""*""-"]
学新通

这个地方可以多理解几次,里面的逻辑按步骤划分之后,再来写实现代码,代码实现如下:

  1.  
    // 只考虑0-9的数字,即单个数字的情况
  2.  
    func converExpressionToSuffixExpression(_ expressionStr: String) -> [String] {
  3.  
        var suffixExpressionList: [String] = []
  4.  
        var operatorExpressionList: [String] = []
  5.  
        
  6.  
        for item in expressionStr {
  7.  
            let itemStr = String(item)
  8.  
            if itemStr == " " {
  9.  
                continue
  10.  
            }
  11.  
     
  12.  
            print(suffixExpressionList)
  13.  
            print(operatorExpressionList)
  14.  
            print("\n")
  15.  
            
  16.  
            if item.isNumber == true {
  17.  
                // 是数字则放入表达式中
  18.  
                suffixExpressionList.append(itemStr)
  19.  
            }
  20.  
            else {
  21.  
                if operatorExpressionList.count == 0 {
  22.  
                    operatorExpressionList.append(itemStr)
  23.  
                }
  24.  
                else {
  25.  
                    // 是运算符,包含"  - * / ( )"
  26.  
                    if itemStr == ")" {
  27.  
                        // 遇到")",则把数组中的运算符弹出,放入到表达式末尾,直到遇到"("停止
  28.  
                        let temp: (l1: [String], l2: [String]) = handleAppendExpressionList(operatorExpressionList, suffixList: suffixExpressionList, isRightBracket: true)
  29.  
                        operatorExpressionList = temp.l1
  30.  
                        suffixExpressionList = temp.l2
  31.  
                    }
  32.  
                    else {
  33.  
                        // 比较运算符的优先级 * / 大于   -
  34.  
                        // 如果 item 不大于当前数组里最后一个元素,则数组里最后一个元素弹出,直到优先级大于最后一个元素为止,item 加入
  35.  
                        // 如果 item 比较中,遇到 ( 且,item 不是 ),则停止
  36.  
                        let lastStr = operatorExpressionList.last
  37.  
                        let isItemPriorityHigh = isFirstOperatorPriorityHigh(first: itemStr, second: lastStr!)
  38.  
                        if isItemPriorityHigh || itemStr == "(" || lastStr == "(" {
  39.  
                            // item运算符比 last 高,则直接入栈
  40.  
                            operatorExpressionList.append(itemStr)
  41.  
                        }
  42.  
                        else {
  43.  
                            let temp: (l1: [String], l2: [String]) = handleAppendExpressionList(operatorExpressionList, suffixList: suffixExpressionList, isRightBracket:  false)
  44.  
                            operatorExpressionList = temp.l1
  45.  
                            suffixExpressionList = temp.l2
  46.  
                            operatorExpressionList.append(itemStr)
  47.  
                        }
  48.  
                    }
  49.  
                }
  50.  
            }
  51.  
        }
  52.  
        
  53.  
        if operatorExpressionList.count > 0 {
  54.  
            repeat {
  55.  
                if let tempLastStr = operatorExpressionList.popLast() {
  56.  
                    suffixExpressionList.append(tempLastStr)
  57.  
                }
  58.  
            } while (operatorExpressionList.count > 0)
  59.  
        }
  60.  
        
  61.  
        return suffixExpressionList
  62.  
    }
  63.  
     
  64.  
    // 处理符号数组到表达式数组逻辑
  65.  
    func handleAppendExpressionList(_ operatorList: [String], suffixList: [String], isRightBracket: Bool) -> ([String], [String]) {
  66.  
        var operatorExpressionList = operatorList
  67.  
        var suffixExpressionList = suffixList
  68.  
        var lastStr = operatorExpressionList.last
  69.  
        repeat {
  70.  
            let tempLastStr = operatorExpressionList.popLast()
  71.  
            if tempLastStr != nil {
  72.  
                lastStr = tempLastStr!
  73.  
                if lastStr != "(" {
  74.  
                    suffixExpressionList.append(tempLastStr!)
  75.  
                }
  76.  
                else {
  77.  
                    if isRightBracket != true { // 只有右括号能消除左括号
  78.  
                        operatorExpressionList.append("(")
  79.  
                    }
  80.  
                }
  81.  
            }
  82.  
            else {
  83.  
                lastStr = ""
  84.  
            }
  85.  
        } while ((lastStr != "(") && (lastStr != ""))
  86.  
        return (operatorExpressionList, suffixExpressionList)
  87.  
    }
  88.  
     
  89.  
     
  90.  
    // 只比较   - * /
  91.  
    func isFirstOperatorPriorityHigh(first: String, second: String) -> Bool {
  92.  
        let isFirst = isMultiplyOrDivideOperator(itemStr: first)
  93.  
        let isSecond = isMultiplyOrDivideOperator(itemStr: second)
  94.  
        if isFirst && !isSecond { // 如果 first 是 * 或者 /,且second 不是 * 或者 /, 则 first高于 second
  95.  
            return true
  96.  
        }
  97.  
        return false
  98.  
    }
  99.  
     
  100.  
    // 判断运算符优先级
  101.  
    func isMultiplyOrDivideOperator(itemStr: String) -> Bool {
  102.  
        if itemStr == "*" ||
  103.  
        itemStr == "x" ||
  104.  
        itemStr == "×" ||
  105.  
        itemStr == "X" ||
  106.  
        itemStr == "/" ||
  107.  
        itemStr == "÷"{
  108.  
            return true
  109.  
        }
  110.  
        return false
  111.  
    }
  112.  
     
  113.  
    //let normalStr = "(8 x (7 - (4 * 1)))"
  114.  
    //let normalStr = "8 - 6 / 4   1"
  115.  
    //let normalStr = "8 - (6 / 4   1)"
  116.  
    //let normalStr = "8 - 6   4 * 1"
  117.  
    let normalStr = "8 - (6   4 / 2 - 1) * 2"
  118.  
    let expressionList = converExpressionToSuffixExpression(normalStr)
  119.  
    print(expressionList)
学新通

后缀表达式的计算

后缀表达式计算的原理

后缀表达式计算的原理如下:

  1. 从左到右遍历数组,遇到运算符后,把运算符 op 前面的两个数字a, b取出,按照 a op b 的逻辑计算,并把三个元素从数组中移除。(这里需要注意移除时的方法,不能一个个移除,移除一个后,数组元素的位置就发生了改变)

  2. 将 运算结果r 插入到数组中计算前 a 的位置

  3. 重复遍历数组,按照上面逻辑计算,直到数组中只有一个元素即结果为止

流程如下:

学新通后缀表达式计算.png

实践如下:

  1.  
    // 初始
  2.  
    ["8""6""4""2""/"" ""1""-""2""*""-"]
  3.  
     
  4.  
    // 从左到右遍历,第一个符号是"/","/"前面的两个元素是"4"和"2",故而把"4/2"的结果放入数组中,把"4", "2", "/"三个元素移出
  5.  
    ["8""6""2.000000"" ""1""-""2""*""-"]
  6.  
     
  7.  
    // 从左到右遍历,第一个符号是" "," "前面的两个元素是"6"和"2.0",故而把"6 2.0"的结果放入数组中,把"6", "2.0", " "三个元素移出
  8.  
    ["8""8.000000""1""-""2""*""-"]
  9.  
     
  10.  
    // 从左到右遍历,第一个符号是"-","/"前面的两个元素是"8.0"和"1",故而把"8.0 - 1"的结果放入数组中,把"8.0", "1", "-"三个元素移出
  11.  
    ["8""7.000000""2""*""-"]
  12.  
     
  13.  
    // 从左到右遍历,第一个符号是"*","*"前面的两个元素是"7.0"和"2",故而把"7.0*2"的结果放入数组中,把"7.0", "2", "*"三个元素移出
  14.  
    ["8""14.000000""-"]
  15.  
     
  16.  
    // 从左到右遍历,第一个符号是"-","-"前面的两个元素是"8"和"14.0",故而把"8-14.0"的结果放入数组中,把"8", "14.0", "-"三个元素移出
  17.  
    // 最后只剩一个元素-6.0,即最终运算结果
  18.  
    ["-6.000000"]
  19.  
     
  20.  
    // 最后得到结果
  21.  
    8 - (6   4 / 2 - 1) * 2 = -6.0
学新通

计算代码如下:

  1.  
    // 后缀表达式的计算
  2.  
    func calculatorExpressionList(_ expressionList: [String]) -> Double {
  3.  
        
  4.  
        if expressionList.count == 1 {
  5.  
            return (expressionList.first as NSString?)?.doubleValue ?? 0.0
  6.  
        }
  7.  
        
  8.  
        // 计算逻辑如下:
  9.  
        // 从左到右遍历数组,    
  10.  
        var targetList: [String] = expressionList
  11.  
        
  12.  
        for index in 0..<expressionList.count {
  13.  
            let item = expressionList[index]
  14.  
            let isOp = isOperator(item)
  15.  
     
  16.  
            // 遇到运算符后,把运算符 op 前面的两个数字a, b取出,按照 a op b 的逻辑计算
  17.  
            if isOp {
  18.  
                let a = expressionList[index - 2]
  19.  
                let b = expressionList[index - 1]
  20.  
                let r = calculator(a, item, b)
  21.  
                // 将 运算结果r 插入到数组中计算前 a 的位置
  22.  
                targetList[index - 2] = r
  23.  
                // 移出运算过的那两个元素
  24.  
                targetList.removeSubrange(Range(NSRange(location: index-1, length: 2))!)
  25.  
                break
  26.  
            }
  27.  
        }
  28.  
     
  29.  
        print(targetList)
  30.  
        // 重复遍历数组,按照上面逻辑计算,直到数组中只有一个元素即结果为止
  31.  
        return calculatorExpressionList(targetList)
  32.  
    }
  33.  
     
  34.  
    // 计算
  35.  
    func calculator(_ a: String, _ op: String, _ b: String) -> String {
  36.  
        var result: Double = 0.0
  37.  
        
  38.  
        let aValue = (a as NSString).doubleValue
  39.  
        let bValue = (b as NSString).doubleValue
  40.  
        
  41.  
        switch op {
  42.  
        case " ":
  43.  
            result = aValue   bValue
  44.  
        case "-":
  45.  
            result = aValue - bValue
  46.  
        case "*""×""x""X":
  47.  
            result = aValue * bValue
  48.  
        case "/""÷":
  49.  
            if bValue != 0.0 {
  50.  
                result = aValue / bValue
  51.  
            }
  52.  
        default:
  53.  
            break
  54.  
        }
  55.  
        
  56.  
        return String(format: "%f", result)
  57.  
    }
  58.  
     
  59.  
    // 是否是运算符
  60.  
    func isOperator(_ str: String) -> Bool {
  61.  
        var result = false
  62.  
        let isMultipleOrDivide = isMultiplyOrDivideOperator(itemStr: str)
  63.  
        if isMultipleOrDivide == false {
  64.  
            if str == " " ||
  65.  
                str == "-" {
  66.  
                result = true
  67.  
            }
  68.  
        }
  69.  
        else {
  70.  
            result = isMultipleOrDivide
  71.  
        }
  72.  
        return result
  73.  
    }
  74.  
     
  75.  
    //        let normalStr = "(8 x (7 - (4 * 1)))"
  76.  
    //        let normalStr = "8 - 6 / 4   1"
  77.  
    //        let normalStr = "8 - (6 / 4   1)"
  78.  
    let normalStr = "8 - 6   4 * 1"
  79.  
    let expressionList = converExpressionToSuffixExpression(normalStr)
  80.  
    print(expressionList)
  81.  
    //let expressionList = converExpressionToSuffixExpression("8 - 6 / 4   1")
  82.  
    //let expressionList = converExpressionToSuffixExpression("8 - (6 / 4   1)")
  83.  
    //let expressionList = converExpressionToSuffixExpression("8 - 6   4 * 1")
  84.  
    let result = calculatorExpressionList(expressionList)
  85.  
    print(normalStr, "=", result)
学新通

补充:

如果只想要表达式的计算结果,不需要使用过程,则可以直接使用 NSExpression计算表达式的结果,代码如下:

  1.  
    // 使用 NSExpression 计算表达式的结果
  2.  
    fileprivate func nsexpressionCalculate() {
  3.  
        let expressionStr = "2   3 * (5 - 1)"
  4.  
        let formatExpressionStr = expressionStr.replacingOccurrences(of: " ", with: "")
  5.  
        let expression = NSExpression(format: formatExpressionStr)
  6.  
        if let result = expression.expressionValue(with: nil, context: nil) as? NSNumber {
  7.  
            print(result)
  8.  
        }
  9.  
    }

总结

学新通swift_中缀表达式计算.png

代码链接:https://github.com/mokong/ExpressionCalculator

代码效果:

学新通pagecallback.gif

参考

1.中缀表达式(https://baike.百度.com/item/中缀表达式/2725244)

2.后缀表达式(https://baike.百度.com/item/逆波兰式/128437?fromtitle=后缀表达式&fromid=6160580)

3.用栈实现表达式的求值(iOS计算器的实现)(https://daimajiaoliu.com/daima/60bd382b7ce6c01)

学新通

学新通

也许你还想看

(▼点击文章标题或封面查看)

图片中多个二维码选择的实现

2022-05-12

学新通

iOS虚拟定位原理与预防

2021-12-16

学新通

MotionLayout动画从未如此简单!

2021-07-15

学新通

iOS包体积优化实践

2022-05-19

学新通

探秘AutoreleasePool实现原理

2022-05-26

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbkkbf
系列文章
更多 icon
同类精品
更多 icon
继续加载