python 实现小学四则运算

小学四则运算

1. 项目要求

  • 随机生成小学内的四则运算式子,不包含负数
  • 支持真分数运算

2. PSP流程表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 30
Development 开发 300 420
Analysis 需求分析 (包括学习新技术) 30 45
Coding Standard 代码规范 (制定合适的规范) 20 25
Design 具体设计 30 40
Coding 具体编码 30 60
Code Review 代码复审 30 30
Test 测试 40 60
Reporting 报告 20 30
合计 520 740

3. 解题思路描述

  该问题总体可分为两部分: 第一部分生成算式, 第二部分计算算式

3.1 第一部分

  首先,随机生成操作数和运算符,通过参数设置操作数的大小上限、运算符的数量以及是否含分数。
  接着,在算式中插入括号,括号插在减法的减数位置上或者乘除法附近的加减子算式中。
  最后,将算式拼接起来。

3.2 第二部分

  通过栈先将算式转为后缀表达式即逆波兰式,之后再计算出算式的结果,结果保留分数形式。

4. 代码说明

4.1 项目文件结构
模块 功能
main.py 主函数
stack.py 栈实现
formula_library.py 存放算式和正确答案
methods.py 包含美化命令行界面、获取参数、输出算式、核对答案
generate_ari.py 生成算式
calculator.py 计算算式结果,含中缀表达式转后缀表达式

  核心模块为generate_ari类和calculator类

  

4.2 部分模块代码

generate_ari类

点击查看代码
import re
import random
from fractions import Fraction


class GenerateAri():
    '''生成算术表达式'''
    def __init__(self, maxNum=10, optNum=1, hasFraction=False):
        self.init_operators(optNum, hasFraction)
        self.init_operands(maxNum, hasFraction)
        self.merge_expression()

    def init_operand(self, maxNum, hasFraction):
        '''生成随机数'''
        if hasFraction:
            while True:
                numerator = random.randint(1, maxNum)
                denominator = random.randint(1, maxNum)
                if numerator != denominator:
                    break
            return Fraction(numerator, denominator)
        else:
            return random.randint(2, maxNum)

    def init_operators(self, optNum, hasFraction):
        '''生成操作符'''
        if hasFraction:
            self.opts = [random.choice(['+', '-', '×', '÷']) for i in range(optNum)]
        else:
            self.opts = [random.choice(['+', '-', '×']) for i in range(optNum)]
        self.opts.append('=')
        self.insert_bracket()

    def init_operands(self, maxNum, hasFraction):
        '''生成操作数'''
        self.nums = [str(self.init_operand(maxNum, hasFraction)) for i in range(len(self.opts))]

    def insert_bracket(self):
        '''插入括号'''
        optsNum = len(self.opts)
        leftBrackets = [''] * optsNum
        rightBrackets = [''] * optsNum
        begin, index = 0, 0
        strOpt = ''.join(self.opts)

        while begin < optsNum-1:
            if self.opts[begin] == '+':
                begin += 1
                continue

            search = None
            if begin-index > 1:
                if self.opts[begin] in ['×', '÷']:
                    search = re.search(r'[+|-]', strOpt[index:begin][::-1])
                    if search:
                        rightBrackets[begin] = ')'
                        leftInd = random.randint(index, begin-search.start()-1)
                        leftBrackets[leftInd] = '('
                        index = begin

            if index >= optsNum-2:
                break

            search = None
            if self.opts[begin] in ['×', '÷', '-']:
                if self.opts[begin] == '÷':
                    search = re.search(r'[×|÷|+|-]', strOpt[begin+1:])
                else:
                    search = re.search(r'[+|-]', strOpt[begin+1:])
                if search:
                    leftBrackets[begin+1] = '('
                    rightInd = random.randint(begin+2+search.start(), optsNum-1)
                    rightBrackets[rightInd] = ')'
                    index = rightInd
                    begin = index
                else:
                    begin += 1

        self.brackets = [leftBrackets, rightBrackets]

    def merge_expression(self):
        '''合并表达式'''
        self.expression = [
            ''.join([i, j, k, l])
            for i, j, k, l in zip(self.brackets[0], self.nums, self.brackets[1], self.opts)
            ]
        self.expression = ''.join(self.expression)
        for _ in set(re.findall(r'[=|×|÷|+|-]', self.expression)):
            self.expression = self.expression.replace(_, ' {} '.format(_))

  
calculator类

点击查看代码
import stack
from fractions import Fraction


class Calculator:
    def __init__(self):
        self.operators = ['+', '-', '×', '÷', '(', ')', '=']
        self.priority = {'+': 1, '-': 1, '×': 2, '÷': 2, '(': 99}

    def getResult(self):
        '''计算结果'''
        valueStack = []
        for item in self.suffixExp:
            if item in self.operators:
                x2 = valueStack.pop()
                x1 = valueStack.pop()
                result = self.calculate(x1, x2, item)
                if result < 0:
                    return '-1'
                valueStack.append(str(result))
            else:
                valueStack.append(item)
        return valueStack[0]

    def calculate(self, x1, x2, opt):
        '''操作数出栈进行计算'''
        if '/' in x1 or '/' in x2:
            x1 = Fraction(x1)
            x2 = Fraction(x2)
        else:
            x1 = eval(x1)
            x2 = eval(x2)

        if opt == '+':
            return x1 + x2
        elif opt == '-':
            return x1 - x2
        elif opt == '×':
            return x1 * x2
        elif opt == '÷':
            return Fraction(x1 / x2).limit_denominator() if x2 != 0 else -1
        else:
            return -1

    def infix2suffix(self, ari):
        '''中缀表达式转后缀表达式'''
        self.suffixExp = []
        optStack = stack.Stack()
        exps = list(zip(ari.brackets[0], ari.nums, ari.brackets[1], ari.opts))
        for exp in exps:
            for element in exp:
                if element == '=':
                    while not optStack.is_empty():
                        popChar = optStack.pop()
                        if popChar != '(':
                            self.suffixExp.append(popChar)
                    break
                if element in self.operators:
                    if optStack.is_empty():
                        optStack.push(element)
                    else:
                        if element == ')':
                            while not optStack.is_empty() and optStack.peek() != '(':
                                popChar = optStack.pop()
                                self.suffixExp.append(popChar)
                            if optStack.size() > 0:
                                optStack.pop()
                        elif self.priority[optStack.peek()] < self.priority[element]:
                            optStack.push(element)
                        else:
                            if optStack.peek() == '(':
                                optStack.push(element)
                            else:
                                while not optStack.is_empty() and self.priority[optStack.peek()] >= self.priority[element] and optStack.peek() != '(':
                                    popChar = optStack.pop()
                                    self.suffixExp.append(popChar)
                                optStack.push(element)
                else:
                    if element != '':
                        self.suffixExp.append(element)

  
methods模块

点击查看代码
import sys
import time
import datetime
from fractions import Fraction


def entrance():
    '''程序开始提示语'''
    print('{:-^60}'.format(''))
    print('{:^60}'.format(''))
    print('{:^55}'.format('四 则 运 算(不 含 负 数)'))
    print('{:^60}'.format(''))
    print('{:-^60}'.format(''))

    for word in '  欢迎使用【四则运算】v1.0':
        sys.stdout.write(word)
        sys.stdout.flush()
        time.sleep(0.2)
    time.sleep(1)
    sys.stdout.write('
{:<50}
'.format(''))
    sys.stdout.flush()


def getParameters():
    '''获取用户输入的算式参数'''
    for word in '  请输入您希望的算式参数:':
        sys.stdout.write(word)
        sys.stdout.flush()
        time.sleep(0.2)
    time.sleep(0.5)
    print()
    while True:
        print('{:^60}'.format('*'*30))
        try:
            formulaNum = int(input('{0:15} {1:>15} | '.format(' ', '算式数量(>=1)')))
            assert formulaNum >= 1
            maxNum = int(input('{0:15} {1:>15} | '.format(' ', '数值上限(>=10)')))
            assert maxNum >= 10
            operatorNum = int(input('{0:15} {1:>14} | '.format(' ', '运算符个数(>=1)')))
            assert operatorNum >= 1
            hasFraction = int(input('{0:15} {1:>13} | '.format(' ', '是否包含分数(0, 1)')))
            assert hasFraction == 0 or hasFraction == 1
            print('{:^60}'.format('*'*30))
        except AssertionError:
            print('{:^60}'.format('*'*30))
            print('  ❌ 输入错误, 请输入正确范围下的数值')
            print('{:-^60}'.format(''))
            print('  请输入您希望的算式参数:')
        except ValueError:
            print('{:^60}'.format('*'*30))
            print('  ❌ 输入错误, 请输入正确的数值')
            print('{:-^60}'.format(''))
            print('  请输入您希望的算式参数:')
        else:
            print('{:-^60}'.format(''))
            break
    return formulaNum, maxNum, operatorNum, hasFraction


def getOutputMode():
    '''获取输出模式'''
    while True:
        print('{:^51}'.format('请选择算式输出模式'))
        print('{0}
{1}'.format('    1、普通模式', '    2、问答模式'))

        try:
            mode = int(input('  请选择: '))
            assert 1 <= mode <= 2
        except AssertionError:
            print(' ❌ 输入错误, 请输入正确范围下的数值')
            print('{:-^60}'.format(''))
        except ValueError:
            print(' ❌ 输入错误, 请输入正确的数值')
            print('{:-^60}'.format(''))
        else:
            print('{:-^60}'.format(''))
            break
    return mode


def checkAnswer(userAnswer, library, titleNum=0):
    '''核对答案'''
    if isinstance(userAnswer, list):
        checks = []
        for result in userAnswer:
            try:
                result = str(Fraction(result).limit_denominator())
                checks = checks + [True] if result == library.results[titleNum] else checks + [False]
            except Exception:
                checks = checks + [False]
            finally:
                titleNum += 1
        return checks
    else:
        try:
            check = True
            userAnswer = str(Fraction(userAnswer).limit_denominator())
            if userAnswer != library.results[titleNum]:
                check = False
        except ValueError:
            check = False
        finally:
            return check


def outputFormula(mode, library):
    '''输出算式'''
    if mode == 1:
        userAnswers = []
        start = datetime.datetime.now()
        for num, exp in enumerate(library.formulas):
            userAnswers = userAnswers + [input('({}) {}'.format(num+1, exp))]
        end = datetime.datetime.now()
        checks = checkAnswer(userAnswers, library)
        errorIndex = [ind for ind, value in enumerate(checks) if value is False]
        if len(errorIndex) > 0:
            print(' ❌ 错误 {} 题: '.format(len(errorIndex)), end='')
            for ind in errorIndex:
                print('({})'.format(ind+1), end=' ')
            print()
            print(' ⭕ 正确答案: ', end='')
            for ind in errorIndex:
                print('({}) {}'.format(ind+1, library.results[ind]), end=' ')
            print()
        else:
            print(' ✅ 恭喜你全部答对了')
        print()
        print('正确率: {:.2f}%'.format(100*float((library.formulaNum()-len(errorIndex))/library.formulaNum())))
        print('耗时: {}'.format(end-start))

    else:
        errorCount = 0
        start = datetime.datetime.now()
        for num, exp in enumerate(library.formulas):
            userAnswer = input('({}) {}'.format(num+1, exp))
            if checkAnswer(userAnswer, library, num):
                print('⭕ 回答正确!!! 正确答案是: {}'.format(library.results[num]))
            else:
                errorCount += 1
                print('❌ 回答错误!!! 正确答案是: {}'.format(library.results[num]))
        end = datetime.datetime.now()
        print()
        print('正确率: {:.2f}%'.format(100*float((library.formulaNum()-errorCount)/library.formulaNum())))
        print('耗时: {}'.format(end-start))

  

5. 测试运行

5.1 正确运行

image
image
image
image

  

5.2 错误输入

image
image
image

原文地址:https://www.cnblogs.com/Lincoln-Wong/p/15348545.html