diff --git a/.idea/chemical_equation_balancer.iml b/.idea/chemical_equation_balancer.iml index 8e5446a..4bad9f7 100644 --- a/.idea/chemical_equation_balancer.iml +++ b/.idea/chemical_equation_balancer.iml @@ -10,5 +10,6 @@ \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 0000000..640fd80 --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/equation_solver.py b/equation_solver.py index ffc7003..c1683f7 100644 --- a/equation_solver.py +++ b/equation_solver.py @@ -1,3 +1,9 @@ +from fractions import Fraction + +import numpy as np +import scipy as sp + + def solve_equation(eq): """ 配平化学方程式,返回配平后的化学方程式 @@ -15,12 +21,94 @@ def solve_equation(eq): :return: 配平后的化学方程式,与输入格式相同 若无法配平,则返回 None """ - # 使用加减消元法解方程组 - # 依据:反应前后,每个元素的个数相等 - - # 用二维数组表示方程组的系数,用一维数组表示方程组的常数项 - + # 统计所有元素的种类 + elements = set() + for each in eq['left']: + for atom in each['atoms']: + elements.add(list(atom.keys())[0]) + for each in eq['right']: + for atom in each['atoms']: + elements.add(list(atom.keys())[0]) + elements = list(elements) + # 构造系数矩阵 + matrix = [] + constant = [] + for atom in elements: + # 遍历左边,左侧系数为正 + row = [] + for each in eq['left']: + for atom_ in each['atoms']: + if atom in atom_: + row.append(atom_[atom]) + break + else: + row.append(0) + # 遍历右边,右侧系数为负 + for each in eq['right']: + for atom_ in each['atoms']: + if atom in atom_: + row.append(-atom_[atom]) + break + else: + row.append(0) + # 取出row中的最后一个元素,反转后加入常数项 + constant.append(-row.pop()) + matrix = np.mat(matrix, int) + print(matrix) + print(constant) + # 求解线性方程组 + try: + result = sp.linalg.solve(matrix, constant).tolist() + except Exception as e: + print(e.args[0]) + print('无解') + return None + # 将结果写入化学方程式,最后一个生成物的系数需要计算得到 + last_substance = eq['right'][-1] + last_atom = list(last_substance['atoms'][0].keys())[0] + # 计算除最后一种生成物外的所有生成物包含last_atom的系数之和 + sum_ = 0 + index = 0 + for each in eq['left']: + for atom in each['atoms']: + if last_atom in atom: + # 该生成物包含last_atom,则获得last_atom的总个数,等于系数*原子个数 + sum_ += result[index] * atom[last_atom] + break + index += 1 + for each in eq['right'][:-1]: + for atom in each['atoms']: + if last_atom in atom: + sum_ -= result[index] * atom[last_atom] + break + index += 1 + result.append(sum_ / last_substance['atoms'][0][last_atom]) + result = expand_to_int(*result) + for i, each in enumerate(eq['left']): + each['coefficient'] = result[i] + for i, each in enumerate(eq['right']): + each['coefficient'] = result[i + len(eq['left'])] + return eq +def expand_to_int(*args): + """ + 将一系列小数同时扩大,全部转换为最接近的整数 + :param args: 一系列小数 + :return: 一系列整数 + """ + # 将所有小数转换为分数 + fractions = [] + for each in args: + fractions.append(Fraction(each).limit_denominator()) + # 计算所有分数的最小公倍数 + lcm = 1 + for each in fractions: + lcm = lcm * each.denominator // np.gcd(lcm, each.denominator) + # 将所有分数扩大为最小公倍数 + result = [] + for each in fractions: + result.append(each.numerator * lcm // each.denominator) + return result \ No newline at end of file diff --git a/fraction.py b/fraction.py deleted file mode 100644 index ef36fdb..0000000 --- a/fraction.py +++ /dev/null @@ -1,117 +0,0 @@ -class Fraction: - """ - 代表分数的类,包含分子和分母 - 提供加减乘除运算 - """ - def __init__(self, top, bottom): - self.num = top - self.den = bottom - - def __str__(self): - return str(self.num) + "/" + str(self.den) - - def __add__(self, otherfraction): - newnum = self.num * otherfraction.den + self.den * otherfraction.num - newden = self.den * otherfraction.den - return Fraction(newnum, newden) - - def __sub__(self, otherfraction): - newnum = self.num * otherfraction.den - self.den * otherfraction.num - newden = self.den * otherfraction.den - return Fraction(newnum, newden) - - def __mul__(self, otherfraction): - newnum = self.num * otherfraction.num - newden = self.den * otherfraction.den - return Fraction(newnum, newden) - - def __truediv__(self, otherfraction): - newnum = self.num * otherfraction.den - newden = self.den * otherfraction.num - return Fraction(newnum, newden) - - def __eq__(self, otherfraction): - firstnum = self.num * otherfraction.den - secondnum = otherfraction.num * self.den - return firstnum == secondnum - - def __gt__(self, otherfraction): - firstnum = self.num * otherfraction.den - secondnum = otherfraction.num * self.den - return firstnum > secondnum - - def __lt__(self, otherfraction): - firstnum = self.num * otherfraction.den - secondnum = otherfraction.num * self.den - return firstnum < secondnum - - def __ge__(self, otherfraction): - firstnum = self.num * otherfraction.den - secondnum = otherfraction.num * self.den - return firstnum >= secondnum - - def __le__(self, otherfraction): - firstnum = self.num * otherfraction.den - secondnum = otherfraction.num * self.den - return firstnum <= secondnum - - def __ne__(self, otherfraction): - firstnum = self.num * otherfraction.den - secondnum = otherfraction.num * self.den - return firstnum != secondnum - - def get_num(self): - return self.num - - def get_den(self): - return self.den - - def __radd__(self, otherfraction): - return self - - -# 约分函数:将分数约分为最简分数 -def reduce_fraction(fraction): - """ - 将分数约分为最简分数 - - :param fraction: 分数,Fraction 类型 - :return: 约分后的分数,Fraction 类型 - """ - # 求最大公约数 - def gcd(a, b): - if b == 0: - return a - else: - return gcd(b, a % b) - - g = gcd(fraction.get_num(), fraction.get_den()) - return Fraction(fraction.get_num() // g, fraction.get_den() // g) - - -# 通分函数:将多个分数通分 -def common_denominator(fractions): - """ - 将多个分数通分 - - :param fractions: 分数列表,Fraction 类型 - :return: 通分后的分数列表,Fraction 类型 - """ - # 求最大公约数 - def gcd(a, b): - if b == 0: - return a - else: - return gcd(b, a % b) - - # 求最小公倍数 - def lcm(a, b): - return a * b // gcd(a, b) - - # 通分 - denominator = 1 - for fraction in fractions: - denominator = lcm(denominator, fraction.get_den()) - for i in range(len(fractions)): - fractions[i] = Fraction(fractions[i].get_num() * denominator // fractions[i].get_den(), denominator) - return fractions diff --git a/main.py b/main.py index 54b4a2f..1e43525 100644 --- a/main.py +++ b/main.py @@ -4,14 +4,17 @@ # 按 双击 ⇧ 在所有地方搜索类、文件、工具窗口、操作和设置。 import parser -from validity_check import IllegalAtomException +import equation_solver # 按间距中的绿色按钮以运行脚本。 if __name__ == '__main__': - eq = input('请输入化学方程式:') - try: - print(parser.parse_equation(eq)) - except IllegalAtomException as e: - print(e.args[0]) + while True: + eq = input('请输入化学方程式:') + try: + eq = parser.parse_equation(eq) + equation_solver.solve_equation(eq) + print('化学方程式:', parser.format_equation(eq)) + except Exception as e: + print(e.args[0]) # 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助 diff --git a/parser.py b/parser.py index dda3f7d..a2c2df5 100644 --- a/parser.py +++ b/parser.py @@ -156,3 +156,27 @@ def parse_equation(eq): 'left': left, 'right': right } + + +def format_molecule(molecule): + """ + 化学式的字典表示转化为字符串 + :param molecule: 化学式的字典表示 + :return: None + """ + if molecule['coefficient'] != 1: + return str(molecule['coefficient']) + molecule['pretty_name'] + else: + return molecule['pretty_name'] + +def format_equation(eq): + """ + 将化学方程式的字典表示转化为字符串,需要包含系数 + :param eq: 化学方程式的字典表示 + :return: 化学方程式的字符串表示 + """ + left = eq['left'] + right = eq['right'] + left = [format_molecule(molecule) for molecule in left] + right = [format_molecule(molecule) for molecule in right] + return ' + '.join(left) + ' => ' + ' + '.join(right) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..adfd4e9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy~=1.23.5 +scipy~=1.9.3 \ No newline at end of file