VBA编程练习03. 计算混合运算表达式结果

论坛 期权论坛 期权     
完美Excel   2019-7-20 10:01   2765   0
学习Excel技术,关注微信公众号:
excelperfect

在前面的文章中,我们用VBA实现了栈数据结构,也实现了算术表达式的拆分,并实现了将拆分后的算术表达式由中缀表达式转换成计算机容易计算的后缀表达式,具体内容详见下列4篇文章:
1.基础扩展 | 13. 使用VBA实现栈结构
2.基础扩展 | 14. 栈结构应用基础示例
3.VBA编程练习01. 拆分算术表达式
4.VBA编程练习02. 中缀表达式转换成后缀表达式

练习:根据上述基础知识,编写实现四则混合运算的VBA代码。

运用前面的知识,我们可以将示例表达式:
9 + (3 – 1) * 3 + 10 /2
转换成后缀表达式:
9 3 1 – 3 * + 10 2 / +

然后,应用下面的规则来计算表达式值。

规则:从左到右遍历算术表达式的每个数字和符号,遇到是数字就入栈,遇到是符号,就将处于栈顶的两个数字出栈,并进行运算,将运算结果入栈,一直到最终获得结果。

关键点:使用栈结构来存放数字,并在遇到符号时将栈顶的两个数字出栈并使用符号进行运算,再将得到的结果入栈。详细的原理请参见程杰著的《大话数据结构》P106-107中的内容

VBA代码:
'创建栈
Dim myStackTwo As NewStack

'计算表达式值
Sub MyCalculator()
    Dim strMyExpress As String
    Dim CalExpress() As String
    Dim NumOne, NumTwo, NumTemp
    Dim i As Long
   
    strMyExpress = "9+(3-1)*3+10/2"

    CalExpress = ConvertExpress(strMyExpress)
   
    For i = LBound(CalExpress) To UBound(CalExpress)
       '如果是数字,则入栈
        If IsNumeric(CalExpress(i)) Then
            myStackTwo.Push CalExpress(i)
        Else
       '否则位于栈顶的两个数字出栈
            NumOne = myStackTwo.Pop
            NumTwo = myStackTwo.Pop
           '根据符号进行运算
            Select Case CalExpress(i)
                Case "+"
                    NumTemp = NumTwo + NumOne
                Case "-"
                    NumTemp = NumTwo - NumOne
                Case "*"
                    NumTemp = NumTwo * NumOne
                Case "/"
                    NumTemp = NumTwo / NumOne
            End Select
           '将计算结果入栈
            myStackTwo.Push NumTemp
        End If
    Next i
   
    '此时在栈顶的元素就是计算结果
    Debug.Print "计算结果:" & myStackTwo.Pop
End Sub

代码中,用到了《VBA编程练习02.中缀表达式转换成后缀表达式》中的程序,我将其改写成了一个返回数组的函数:
'创建栈
Dim MyStack As NewStack

'转换成后缀表达式
Function ConvertExpress(strExpress As String) As String()
    Dim SuffixExpress As String
    '声明放置表达式各元素的数组
    Dim MidExpress() As String
    Dim BackExpress() As String
    '声明其它变量
    Dim i As Long, j As Long
    Dim iCount As Long
    Dim str
      
    MidExpress = SplitExpress(strExpress)
   
    '遍历表达式
    For i = LBound(MidExpress) To UBound(MidExpress)
       '如果是数字则直接放入输出数组
        If IsNumeric(MidExpress(i)) Then
            iCount = iCount + 1
            ReDim Preserve BackExpress(1 ToiCount)
            BackExpress(iCount) = MidExpress(i)
        '如果是符号则根据括号配对或者四则运算规则
       '将运算符号放入输出数组
        Else
            Select Case MidExpress(i)
                Case "{","[", "("
                    MyStack.Push MidExpress(i)
                Case "}","]", ")"
               '遇到右括号,则栈顶元素出栈
               '直到遇到配对的左括号
                    str = MyStack.StackTop
                    Do While ((str "{") And (str  "[") And (str "("))
                        iCount = iCount + 1
                        ReDim Preserve BackExpress(1 To iCount)
                        BackExpress(iCount) =MyStack.Pop
                        str = MyStack.StackTop
                    Loop
                    MyStack.Pop
               '乘除优先级最大直接入栈
                Case "*","/"
                    str = MyStack.StackTop
                    MyStack.Push MidExpress(i)
               '加减时遇到栈顶为乘除
               '则全部出栈并将其放入输出数组
                Case "+","-"
                    str = MyStack.StackTop
                    If str = "*" Orstr = "/" Then
                        Do While Not MyStack.StackEmpty
                            iCount = iCount + 1
                            ReDim Preserve BackExpress(1 To iCount)
                            BackExpress(iCount)= MyStack.Pop
                        Loop
                    End If
                    MyStack.Push MidExpress(i)
            End Select
        End If
    Next i
   
    '将栈中剩余的元素全部出栈并放入输出数组
    Do While Not MyStack.StackEmpty
        iCount = iCount + 1
        ReDim Preserve BackExpress(1 To iCount)
        BackExpress(iCount) = MyStack.Pop
    Loop
   
    ConvertExpress = BackExpress()
End Function

上面的代码又用到了《VBA编程练习01.拆分算术表达式》中的程序,我也将其改写成了一个返回数组的函数:
Function SplitExpress(express As String) As String()
    '存储表达式的每个字符
    Dim var1() As String
    '存储表达式中各元素(符号和数字)
    Dim var2() As String
    '循环变量
    Dim i As Long
    Dim j As Long
    '计数,用来确定动态数组大小
    Dim iCount As Long
    '表达式长度
    Dim lLen As Long
    '临时变量,用来存储数字元素中单个数字数
    Dim temp As Long
    '将相邻的数字组合成一个数字元素
    Dim str As String
   
    lLen = Len(express)
    '重定义数组大小为表达式长度
    '比表达式的长度大1
    '确保最后一个字符能添加到数组
    ReDim var1(1 To lLen + 1)
   
    '将表达式拆分单个字符
    For i = 1 To lLen
        var1(i) = Mid(express, i, 1)
    Next i
   
    temp = 0
   
    '遍历表达式
    For i = 1 To lLen + 1
       '如果相邻字符是数字,则将其取出并连接成一个数字
        If var1(i) Like "[0-9]" Then
            temp = temp + 1
        ElseIf temp > 0 Then
            For j = 1 To temp
                str = str & var1(i - temp +j - 1)
            Next j
            iCount = iCount + 1
            ReDim Preserve var2(1 To iCount)
            var2(iCount) = str
            temp = 0
            str = ""
        End If
      
       '如果是符号,则直接存放
        Select Case var1(i)
            Case "{", "[","(", ")", "}", "]", "+","-", "*", "/"
                iCount = iCount + 1
                ReDim Preserve var2(1 ToiCount)
                var2(iCount) = var1(i)
        End Select
    Next i
   
    '返回结果
    SplitExpress = var2()
End Function

运行MyCalculator过程的结果为:



下面是代码的图片版:



分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:195
帖子:39
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP