Python tkinter 实现带括号功能的计算器

一. 中缀表达式与后缀表达式:

1. 简介

中缀表达式的特征是运算符的位置在两个参数之间,例如“13+14×10”、“18÷5+12”。一看便知,中缀表达式就是自然运算式,只不过现在为了有别于后缀表达式,另起了一个名字。

2. 转换

现在来讲解一下如何将中缀表达式转换成后缀表达式。在 Python 代码中可以创建两个空列表,一个用来临时存储运算符,另一个用来存储后缀表达式的列表形式。比如对于中缀表达式“13+14×10”,遍历它,首先将 13 存到后缀表达式列表中索引 0 的位置;第二次循环将 ‘+’ 存到运算符列表中;第三次把 14 存到后缀表达式中,索引为1;接下来遇到‘×’,将它与已经存在于运算符列表的 ‘+’ 进行比较,因为数学运算规则中乘法优先级高于加法,所以将 ‘×’ 存到运算符列表 ‘+’ 之后的位置(反之,如果是先遇到 ‘×’ 后遇到 ‘+’,则将 ‘×’ 弹出并追加到后缀表达式列表的最后位置);第五次遍历,将 10 存到后缀表达式列表索引 2 的位置。中缀表达式已经遍历完成,下面将运算符列表中的元素倒序追加到后缀表达式列表末尾,最终生成的后缀表达式列表就是 [13,14,10,'×','+']。

中缀表达式遍历元素运算符列表后缀表达式列表
13[][13]
'+'['+'][13]
14['+'][13,14]
'×'['+','×'][13,14]
10['+','×'][13,14,10]
已遍历完['+','×'][13,14,10,'×','+']

3. 运算

接下来讲一下后缀表达式的运算步骤。需要用到一个临时的栈。在这个项目里,只需实现一个简单的栈即可。还是用上边这个例子,遍历这个列表,如果遇到数字,就压入栈;如果遇到运算符,就把栈顶的两个数字弹出,执行相应的运算,将运算结果再压入栈;判断当后缀表达式列表长度为0时结束循环,如果这时栈中只有一个元素,弹出这个元素并打印,这个元素就是运算结果。

后缀表达式列表
[13,14,10,'×','+'][]
[14,10,'×','+'][13]
[10,'×','+'][14,13]
['×','+'][10,14,13]
['+'][140,13]
[][153]

4. 有括号的情况

括号的运算优先级是最高的,所以要在转换后缀表达式的代码中加一点逻辑。在遍历中缀表达式时如果遇到右括号,就在运算符列表里向前找到左括号的索引值,将从这个索引值加一对应的元素到列表末尾的元素从列表中弹出,追加到后缀表达式列表中,左括号则只弹出,不追加到后缀表达式列表中。

二. 计算器的具体实现:

1. 创建一个计算器类:

初始化时只需要定义计算器实例的表达式这一个变量。

def __init__(self, exp):
    self.exp = exp

2. 格式检查:

这个步骤指的是在用户输入了表达式之后,程序会对表达式的逻辑是否正确进行判断,比如连续输入了两个运算符。

def preCheck(self):
    exp = self.exp
    for index in range(len(exp)):
        if exp[index] in "+-×÷" and exp[index - 1] in "+-×÷":
            return False
        if exp[index] == "." and exp[index - 1] == ".":
            return False
    return self.exp

3. 格式改造:

这一步的作用是将用户输入的字符串改造成列表,这样可以使数字和运算符独立存在,方便后续操作。

def formatChange(self):
    self.midfix = []
    tmp = ''
    for index in range(len(self.exp)):
        // 如果第一个字符是负号“-”
        if index == 0 and self.exp[index] == "-":
            tmp += "-"
        // 如果字符是负号“-”且前一个字符是左括号
        elif index > 0 and self.exp[index - 1] == "(" and self.exp[index] == "-":
            tmp += str(self.exp[index])
        // 如果字符是数字或小数点或“π”
        elif self.exp[index].isdigit() or self.exp[index] == '.' or self.exp[index] == 'π':
            tmp += str(self.exp[index])
        else:
            if tmp != '':
                self.midfix.append(tmp)
                tmp = ''
            self.midfix.append(self.exp[index])
    if tmp != '':
        self.midfix.append(tmp)
    return self.midfix

4. 转换后缀表达式:

def makeSuffixExp(self):
    opList = []
    opDict = self.opDict
    self.suffixExpList = []
    for item in self.midfix:
        // 如果是数
        if self.isNumber(item):
            self.suffixExpList.append(item)
        // 如果是π
        if item == 'π':
            self.suffixExpList.append(str(math.pi))
        // 如果是这几种运算符号(+-×÷^!)
        if item in "+-×÷^!":
            if opList == []:
                opList.append(item)
            else:
                if opDict[item] <= opDict[opList[len(opList)-1]]:
                    for index in range(len(opList)):
                        self.suffixExpList.append(opList[index])
                        del opList[index]
                opList.append(item)
        // 如果是左括号
        if item == "(":
            opList.append(item)
        // 如果是右括号
        if item == ")":
            pt = len(opList) - 1
            while opList[pt] != "(":
                pt -= 1
            pt += 1
            for index in range(pt, len(opList)):
                self.suffixExpList.append(opList[index])
                opList.pop(index)
            opList.pop()
    self.suffixExpList.extend(opList[::-1])
    return self.suffixExpList

其中,opDict 是我定义的一个字典,定义了运算符的优先级:

opDict = {"+":1, "-":1, "×":2, "÷":2, "(":0, "^":3, "!":3}

这里也引用了我自定义的一个函数 isNumber,用来判断一个变量是否是数字:

def isNumber(self, num):
    try:
        float(num)
        return True
    except ValueError:
        pass
    try:
        import unicodedata
        unicodedata.numeric(num)
        return True
    except (TypeError, ValueError):
        pass
    return False

5. 运算:

def calculate(self):
    stack = Stack()
    while expLength := len(self.suffixExpList):
        poppedItem = self.suffixExpList.pop(0)
        if poppedItem in "+-×÷^":
            num2 = stack.pop()
            num1 = stack.pop()
            if poppedItem == "+":
                newNum = self.add(num1, num2)
            elif poppedItem == "-":
                newNum = self.sub(num1, num2)
            elif poppedItem == "×":
                newNum = self.mul(num1, num2)
            elif poppedItem == "÷":
                newNum = self.dev(num1, num2)
            else:
                newNum = self.power(num1, num2)
            stack.push(newNum)
        elif poppedItem == "!":
            num = stack.pop()
            newNum = self.factorial(num)
            stack.push(newNum)
        else:
            stack.push(float(poppedItem))
        expLength -= 1
    if len(stack) == 1:
        return stack.pop()

这个函数中引用了几个自定义的运算函数:

// 加
def add(self, num1, num2):
    return num1 + num2
// 减
def sub(self, num1, num2):
    return num1 - num2
// 乘
def mul(self, num1, num2):
    return num1 * num2
// 除
def dev(self, num1, num2):
    return num1 / num2
// 指数
def power(self, num1, num2):
    return math.pow(num1, num2)
// 阶乘
def factorial(self, num):
    result = 1
    for i in range(2, int(num)+1):
        result *= i
    return result

6. 执行函数:

def execute(self):
    if self.preCheck():
        self.formatChange()
        self.makeSuffixExp()
        return self.cancelZero(self.calculate())
    else:
        return "ERROR"

这个函数里引用的cancelZero函数的作用是如果结果的小数位为0,则在显示时去掉小数部分:

def cancelZero(self, num):
    if str(num).endswith(".0"):
        return int(num)
    else:
        return num

7. 栈的实现:

在第 5 步运算这个函数中,使用了 Stack 类的实例,这个类的实现放在这里,就是一个简单的用链表实现的栈:

class Node:

    def __init__(self, data, next=None):
        self.data = data
        self.next = next

class Stack:

    def __init__(self):
        self.head = None

    def __iter__(self):
        def visitNodes(node):
            if node != None:
                visitNodes(node.next)
                tempList.append(node.data)
        tempList = list()
        visitNodes(self.head)
        return iter(tempList)

    def peak(self):
        if self.isEmpty():
            raise KeyError("The stack is empty.")
        return self.head.data
    
    def isEmpty(self):
        return self.head is None

    def __len__(self):
        if cur := self.head:
            self.size = 1
        else:
            return 0
        while cur.next is not None:
            self.size += 1
            cur = cur.next
        return self.size

    def push(self, data):
        self.head = Node(data, self.head)

    def pop(self):
        poppedData = self.head.data
        self.head = self.head.next
        return poppedData

8. 使用 tkinter 搞个可视化界面:

tkinter 在这个项目中的作用就比较简单了,我主要的想法就是在窗口最上部放一个显示屏,下边都是计算器的各种按钮,布局比较简单。为了不逼死强迫症(我就是强迫症),我设计了5X5共25个按钮,在以上的基础上又加入了开根号、清除一个数字的C按钮、清除所有数字的CE按钮。以下是我的代码,抛砖引玉吧:

import tkinter as tk
from turtle import bgcolor
from calculator import Expression

window = tk.Tk()
window.title("计算器")
window.geometry('450x490')
window.configure(background="#4169e1", cursor="mouse")
exp = tk.StringVar()
exp.set('')
entry_panel = tk.Entry(window, textvariable=exp, width=25, font=("Arial", 20), foreground="#00bfff", background="#191970", relief="sunken")
entry_panel.grid(row=0, column=4, columnspan=5)

//输入字符的功能
def enterNum(num):
    if exp.get() == "ERROR":
        exp.set("")
    exp.set(exp.get() + num)

//清空显示屏的功能
def clearEntry():
    exp.set('')

//退格功能
def backspace():
    exp.set(exp.get()[:-1])

//开二次根号功能
def sqrtExec():
    a = Expression(exp.get())
    exp.set(a.sqrtExec())

//计算
def calculate():
    a = Expression(exp.get())
    exp.set(str(a.execute()))

w = 10
h = 5

buttonOne = tk.Button(text='1', width=w, height=h, command=lambda: enterNum('1'), font=(30), background="#00bfff")
buttonOne.grid(row=2, column=4)
buttonTwo = tk.Button(text='2', width=w, height=h, command=lambda: enterNum('2'), font=(30), background="#00bfff")
buttonTwo.grid(row=2, column=5)
buttonThree = tk.Button(text='3', width=w, height=h, command=lambda: enterNum('3'), font=(30), background="#00bfff")
buttonThree.grid(row=2, column=6)
buttonFour = tk.Button(text='4', width=w, height=h, command=lambda: enterNum('4'), font=(30), background="#00bfff")
buttonFour.grid(row=3, column=4)
buttonFive = tk.Button(text='5', width=w, height=h, command=lambda: enterNum('5'), font=(30), background="#00bfff")
buttonFive.grid(row=3, column=5)
buttonSix = tk.Button(text='6', width=w, height=h, command=lambda: enterNum('6'), font=(30), background="#00bfff")
buttonSix.grid(row=3, column=6)
buttonSeven = tk.Button(text='7', width=w, height=h, command=lambda: enterNum('7'), font=(30), background="#00bfff")
buttonSeven.grid(row=4, column=4)
buttonEight = tk.Button(text='8', width=w, height=h, command=lambda: enterNum('8'), font=(30), background="#00bfff")
buttonEight.grid(row=4, column=5)
buttonNine = tk.Button(text='9', width=w, height=h, command=lambda: enterNum('9'), font=(30), background="#00bfff")
buttonNine.grid(row=4, column=6)
buttonClear = tk.Button(text='CE', width=w, height=h, command=clearEntry, font=(30), background="#1e90ff")
buttonClear.grid(row=2, column=8)
buttonZero = tk.Button(text='0', width=w, height=h, command=lambda: enterNum('0'), font=(30), background="#00bfff")
buttonZero.grid(row=5, column=5)
buttonBackspace = tk.Button(text='C', width=w, height=h, command=backspace, font=(30), background="#1e90ff")
buttonBackspace.grid(row=2, column=7)
buttonAdd = tk.Button(text='+', width=w, height=h, command=lambda: enterNum('+'), font=(30), background="#87cefa")
buttonAdd.grid(row=3, column=7)
buttonMinus = tk.Button(text='-', width=w, height=h, command=lambda: enterNum('-'), font=(30), background="#87cefa")
buttonMinus.grid(row=4, column=7)
buttonMulti = tk.Button(text='×', width=w, height=h, command=lambda: enterNum('×'), font=(30), background="#87cefa")
buttonMulti.grid(row=5, column=7)
buttonDevide = tk.Button(text='÷', width=w, height=h, command=lambda: enterNum('÷'), font=(30), background="#87cefa")
buttonDevide.grid(row=6, column=7)
buttonCalc = tk.Button(text='=', width=w, height=h, command=calculate, font=(30), background="#468284")
buttonCalc.grid(row=6, column=8)
buttonLeftPar = tk.Button(text='(', width=w, height=h, command=lambda: enterNum('('), font=(30), background="#87cefa")
buttonLeftPar.grid(row=6, column=5)
buttonRightPar = tk.Button(text=')', width=w, height=h, command=lambda: enterNum(')'), font=(30), background="#87cefa")
buttonRightPar.grid(row=6, column=6)
buttonDot = tk.Button(text='.', width=w, height=h, command=lambda: enterNum('.'), font=(30), background="#00bfff")
buttonDot.grid(row=6, column=4)
buttonPower = tk.Button(text='^', width=w, height=h, command=lambda: enterNum('^'), font=(30), background="#87cefa")
buttonPower.grid(row=3, column=8)
buttonSqrt = tk.Button(text='√', width=w, height=h, command=sqrtExec, font=(30), background="#87cefa")
buttonSqrt.grid(row=4, column=8)
buttonFact = tk.Button(text='!', width=w, height=h, command=lambda: enterNum('!'), font=(30), background="#87cefa")
buttonFact.grid(row=5, column=8)
buttonDouZero = tk.Button(text='00', width=w, height=h, command=lambda: enterNum('00'), font=(30), background="#00bfff")
buttonDouZero.grid(row=5, column=4)
buttonPi = tk.Button(text='π', width=w, height=h, command=lambda: enterNum('π'), font=(30), background="#00bfff")
buttonPi.grid(row=5, column=6)

window.mainloop()

三. 操作演示:

为了应付作业而编的,一个新手,请大家多多指教。/** * Title: Calculator * Description: * Copyright: Copyright (c) 2004 * Company: CUIT * Calculator.java * Created on 2004年10月13日, 下午2:35 * @author jacktom * @version 1.0*/import java.awt.*;import java.awt.event.*;import javax.swing.*;public class Calculator extends JFrame implements ActionListener{ Operator oper; String a,result; int type; boolean flag1=false; boolean flag2=false; boolean judge=true; int count=0; JTextField text; JPanel jpanel[]; JPanel jpanel1; JButton jbutton[]; String name[]={"0",".","-/+","+","=","1","2","3","-",")","4","5","6","*","(","7","8","9","/","CE"}; //Construct the JFrame public Calculator() { oper=new Operator(); setSize(250,300); setVisible(true); //Overridden so we can exit when window is closed this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } }); Container con=getContentPane(); con.setLayout(new GridLayout(5,5)); text=new JTextField(12); text.setHorizontalAlignment(JTextField.RIGHT); jpanel1=new JPanel(); jpanel1.setLayout(new GridLayout(1,1)); jpanel1.add(text); jpanel=new JPanel[4]; for(int i=0;i<4;i++) { jpanel[i]=new JPanel(); jpanel[i].setLayout(new GridLayout(1,5)); } jbutton=new JButton[name.length]; //add button to panel for(int j=0;j=0;i--) { con.add(jpanel[i]); } } public void actionPerformed(ActionEvent e) { for(int i=0;i<10;i++) { if(e.getActionCommand().equals(String.valueOf(i))) if(flag1==false) { text.setText(String.valueOf(i)); flag1=true; } else { text.setText(text.getText()+i); } } if(e.getActionCommand().equals(".")) if(flag2==false&&count==0) { text.setText(text.getText()+"."); count++; flag1=true; } if(e.getActionCommand().equals("+")||e.getActionCommand().equals("-")||e.getActionCommand().equals("*")||e.getActionCommand().equals("/")) { if(judge) { a=text.getText(); oper.EvaluateExpression(a); } else judge=true; flag1=false; flag2=false; count=0; if(e.getActionCommand().equals("+")) { a="+"; oper.EvaluateExpression(a); } if(e.getActionCommand().equals("-")) { a="-"; oper.EvaluateExpression(a); } if(e.getActionCommand().equals("*")) { a="*"; oper.EvaluateExpression(a); } if(e.getActionCommand().equals("/")) { a="/"; oper.EvaluateExpression(a); } } if(e.getActionCommand().equals("=")) { if(judge) { a=text.getText(); oper.EvaluateExpression(a); } else judge=true; oper.EvaluateExpression("#"); text.setText(""); text.setText(String.valueOf(oper.CalculateResult())); flag1=false; flag2=false; count=0; } if(e.getSource()==jbutton[2]) { text.setText("-"+text.getText()); } if(e.getActionCommand().equals(")")) { a=text.getText(); oper.EvaluateExpression(a); oper.EvaluateExpression(")"); judge=false; } if(e.getActionCommand().equals("CE")) { text.setText(""); judge=true; count=0; flag1=false; flag2=false; oper=new Operator(); } if(e.getActionCommand().equals("(")) { oper.EvaluateExpression("("); } } /** * Main method * * @param args String[] */ public static void main(String args[]) { Calculator Cmain=new Calculator(); Cmain.pack(); }}/** * Operator.java * Description:用栈实现计算 * Created on 2004年10月13日, 下午3:35 * @author jacktom*/public class Operator{ StackY optr; //存放操作符 StackY opnd;//存放操作数 Puzhu p; boolean Mark; Operator() { p=new Puzhu(); optr=new StackY(); opnd=new StackY(); optr.push("#"); } public void EvaluateExpression(String s) { boolean mark=true; if(s=="+"||s=="-"||s=="*"||s=="/"||s=="("||s==")"||s=="#") { while(mark) { switch(p.Precede(optr.peek(),s)) { case -1: optr.push(s); mark=false; break; case 0: optr.pop(); mark=false; break; case 1: String theta=optr.pop(); String a =opnd.pop(); String b =opnd.pop(); if(a.indexOf(".",0)==-1&&b.indexOf(".",0)==-1) Mark=true; else Mark=false; double c=Double.valueOf(a).doubleValue(); double d=Double.valueOf(b).doubleValue(); double e=p.Operate(c,theta,d); String f=String.valueOf(e); if(theta=="/") Mark=false; if(Mark) opnd.push(f.substring(0,f.indexOf(".",0))); else opnd.push(f); break; } } } else opnd.push(s); } public String CalculateResult() { //double result=Double.valueOf(opnd.peek()).doubleValue(); return opnd.peek(); }}/** * Description:判断操作符的优先级并计算结果 * Created on 2004年10月13日, 下午4:00 * @author jacktom*/class Puzhu{ public Puzhu() {} public int Precede(String optr1,String optr2) { String[] A={"+","-","*","/","(",")","#"}; int[][] B={ {1,1,-1,-1,-1,1,1}, {1,1,-1,-1,-1,1,1}, {1,1,1,1,-1,1,1}, {1,1,1,1,-1,1,1}, {-1,-1,-1,-1,-1,0,2}, {1,1,1,1,2,1,1}, {-1,-1,-1,-1,-1,2,0}, }; int i=0,j=0,k; while(i<7) { if(A[i]==optr1) { break; } i++; } while(j<7) { if(A[j]==optr2) { break; } j++; } k=B[i][j]; return k; } public double Operate(double a,String oper,double b) { double c=0; if(oper=="+") c=a+b; if(oper=="-") c=b-a; if(oper=="*") c=a*b; if(oper=="/") c=b/a; return c; }}/** * StackY.java * Description:堆栈的基本操作实现 * Created on 2004年10月13日, 下午3:05 * @author jacktom*/public class StackY { private int maxSize; // size of stack array private String[] stackArray; private int top; // top of stack public StackY(int s) // constructor { maxSize = s; // set array size stackArray = new String[maxSize]; // create array top = -1; // no items yet }public StackY() // constructor { maxSize = 20; // set array size stackArray = new String[maxSize]; // create array top = -1; // no items yet } public void push(String j) // put item on top of stack { top++; stackArray[top] = j; // increment top, insert item } public String pop() // take item from top of stack { return stackArray[top--]; // access item, decrement top } public String peek() // peek at top of stack { return stackArray[top]; } public boolean isEmpty() // true if stack is empty { return (top == 0); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值