由于疫情的影响,高考改到前几天进行。想起我参加高考已经过去6年了,然后想到之前写过一个化学方程式配平的小工具,就顺便拿出来讲讲~
当时做往年的高考真题卷,2个半小时的理综卷,我一般都是1个半小时(生物20min+化学30min+物理40min)就搞定了,考试前信心满满啊。结果到了高考理综的时候,可能是最后一科了,既兴奋且紧张,理综考卷勉强做完了。最终理综考砸了,sad~
好像有点走偏了
主要是想吹嘘一下,下面开始进入正题吧~
项目代码传送门
1. 输入输出
>>> left, right = ('CH2OH(CHOH)4CHO', 'Ag(NH3)2OH'), ('CH2OH(CHOH)4COONH4', 'Ag', 'NH3', 'H2O')
>>> left_solve, right_solve = balance(left, right)
>>> left_solve
[1 2]
>>> right_solve
[1 2 3 1]
>>> get_text(left, right, left_solve, right_solve)
1CH2OH(CHOH)4CHO + 2Ag(NH3)2OH = 1CH2OH(CHOH)4COONH4 + 2Ag + 3NH3 + 1H2O
下面讲解都基于上述例子。
这个配平器是基于质量守恒定律,可以支持绝大部分的化学方程式配平,但不支持下面情况的化学方程式。
离子方程式
例如,
Fe + 2Fe3+ = 3Fe2+
这种方程式暂时不在考虑的范围之内。符合质量守恒定律但不一定会符合电荷守恒定律的方程式
例如
3H2O2 + 2KMnO4 + 3H2SO4 = 4O2 + K2SO4 + 2MnSO4 + 6H2O 5H2O2 + 2KMnO4 + 3H2SO4 = 5O2 + K2SO4 + 2MnSO4 + 8H2O 7H2O2 + 2KMnO4 + 3H2SO4 = 6O2 + K2SO4 + 2MnSO4 + 10H2O
左右两边存在相同的物质
例如生物层面上的呼吸作用表达式
C6H12O6 + 6H2O + 6O2 = 6CO2 + 12H2O
2. 输入字符串解析
我把每个输入物质的字符串分成3个部分:
- 化学元素值。每个元素的首字母为大写,其余字母为小写
- 左右括号。括号内的内容可以看出一个整体的元素。
- 数量值(可省略)。跟在每个元素和右括号后面,表示该元素有多少个。该值可以省略,且默认为1
3. 构建元素生成树
每个输入的字符串构建一棵树(这里我没有选择建立二叉树,因为并没有涉及到运算符操作),建树的流程如下:
- 定义两个缓存数据域,用于缓存化学元素和数量值。定义节点结构,由3份内存空间组成——数据域,父节点指针,子节点域,其中数据域由元素和数值组成。生成一个数据域的数值为1的节点作为当前节点。然后从左往右遍历输入字符串。
- 遇到大写字母时,
- 如果当前元素缓存域为空,则将该字符压入元素缓存域,否则,则说明这是一个新的元素的开端,则先把元素和数字缓存域全部压出,分别作为数据域的元素和数值(如果数字域为空,则默认置1),以此生成一个新的节点,把该节点的父节点指针指向当前节点,并作为当前节点的子节点。
- 最后再将该字符压入元素缓存域。
- 遇到小写字母时,压入元素缓存域。
- 遇到数字时,压入数字缓存域。
- 遇到左括号时,重复步骤3,然后生成一个新的节点,把该节点的父节点指针指向当前节点(注意这里并不会立即把该节点作为当前节点的子节点,因为数字域的数值还没确定),最后把该节点作为当前节点。
- 遇到右括号时,重复步骤3,然后把当前节点压入元素缓存域,最后把当前节点的父节点作为当前节点。
- 重复步骤2-8,直到字符串遍历完毕,如果元素缓存域不为空,则重复3.
通过上述流程,Ag(NH3)2OH
的生成树如下:
[[node,1]]
|
[[Ag,1],[node,2],[O,1],[H,1]]
|
[[N,1],[H,3]]
4. 解析生成树
深度优先遍历每棵生成树,并把相同的元素合并。
这一部分比较简单,看代码理解即可。
Ag(NH3)2OH
的遍历结果如下
{'Ag': 1, 'O': 1, 'H': 7, 'N': 2}
5. 线性代数配平
为每个元素构建如下方程式, \[ \vec{A} \vec{X} - \vec{B} \vec{Y} = 0 \] 其中,\(\vec{A} = [a_1,a_2...a_i]\) 和 \(\vec{B} = [b_1,b_2...b_j]\) 分别表示左右输入中每一个输入物质中包含该元素的数量。
\(n\) 个元素即可得到一个具有 \(n\) 个方程的方程组。
拼接 \(\vec{A}\) 和 \({\vec{B}}\) ,得到矩阵行列式,上面例子中得到的行列式为
[[ 0 1 0 -1 0 0]
[ 0 2 -1 0 -1 0]
[ 6 0 -6 0 0 0]
[ 6 1 -7 0 0 -1]
[ 12 7 -15 0 -3 -2]]
当\(n \ge i+j-1\) 时,方程有唯一解。
解上述矩阵行列式的通解和特解,可得方程的解为
[1. 2. 1. 2. 3. 1.]
6. 化整
最后得出来的解可能是浮点型形式,需要将其全部转化为整型形式。问题就转化为求这些浮点型的最大公约数。
一般的做法是先把小数转化为分数,然后求分母的最小公倍数和分子的最大公约数的比值,但因为精确度的原因,小数转化为分数在计算机层面上很难实现(据说python有现成的包,但我还没有试过其实还是比较懒)。
由于化学配平的系数中,最小一项基本都是小于100,所以最后索性写了个暴力化整的方法,遍历100以内所有最小一项系数的整数形式,输出使所有系数都为整型的最小的结果即可。