21

我正在尝试为一个应用程序编写一个方法,该应用程序采用像“CH3COOH”这样的化学公式并返回某种充满符号的集合。

CH3COOH 将返回 [C,H,H,H,C,O,O,H]

我已经有了一些可以工作的东西,但是它非常复杂,并且使用了大量的代码以及许多嵌套的 if-else 结构和循环。

有没有办法通过使用某种带有 String.split 的正则表达式或其他一些出色的简单代码来做到这一点?

4

5 回答 5

31

我编写了几篇关于如何解析分子式的系列文章,包括更复杂的公式,如 C6H2(NO2)3CH3 。

最近的是我在 PyCon2010 上的演讲“ PLY 和 PyParsing ”,我使用分子式评估器作为我的示例问题比较了这两个 Python 解析系统。甚至还有我的演示视频

该演示文稿基于我使用 ANTLR 开发分子式解析器的三部分系列文章。第 3 部分中,我将 ANTLR 解决方案与手写的正则表达式解析器以及 PLY 和 PyParsing 中的解决方案进行了比较。

regexp 和 PLY 解决方案最初是在一个由两部分组成的系列中开发的,该系列介绍了用 Python 编写解析器的两种方式。

正则表达式解决方案和基础 ANTLR/PLY/PyParsing 解决方案使用像 [AZ][az]?\d* 这样的正则表达式来匹配公式中的项。这就是@David M 的建议。

这是用Python解决的

import re

# element_name is: capital letter followed by optional lower-case
# count is: empty string (so the count is 1), or a set of digits
element_pat = re.compile("([A-Z][a-z]?)(\d*)")

all_elements = []
for (element_name, count) in element_pat.findall("CH3COOH"):
    if count == "":
        count = 1
    else:
        count = int(count)
    all_elements.extend([element_name] * count)

print all_elements

当我运行这个(硬编码使用乙酸,CH3COOH)时,我得到

['C', 'H', 'H', 'H', 'C', 'O', 'O', 'H']

请注意,这段代码假定分子式是正确的。如果你给它“##$%^O2#$$#”之类的东西,那么它将忽略它不知道的字段并给出['O','O']。如果你不想要那个,那么你必须让它更健壮一点。

如果您想支持更复杂的公式,例如 C6H2(NO2)3CH3,那么您需要了解一些树数据结构,特别是(正如@Roman 指出的)抽象语法树(通常称为 AST)。这太复杂了,无法进入这里,因此请参阅我的演讲和论文了解更多详细信息。

于 2010-09-18T18:45:25.457 回答
24

假设它的大小写正确,等式中的每个符号都匹配这个正则表达式:

[A-Z][a-z]*\d*

(对于受化学挑战的元素,元素的符号总是大写字母,后跟可选的小写字母 1 或可能的 2 - 例如 Hg 表示汞)

您可以像这样捕获元素符号和组中的数字:

([A-Z][a-z]*)(\d*)

所以是的,理论上这将是正则表达式可以提供帮助的东西。如果你正在处理像 C 6 H 2 (NO 2 ) 3 (CH 3 ) 3这样的公式,那么你的工作当然会有点困难......

于 2010-06-04T13:08:33.193 回答
12

如果您只需要处理简单的情况,使用正则表达式的解决方案是最好的方法。否则,您需要构建抽象语法树之类的东西并对其进行评估或使用波兰表示法

例如,TNT 公式C6H2(NO2)3CH3应表示为:

(+ (* C 6) (* H 2) (* (+ N (* O 2)) 3) C (+ H 3))
于 2010-06-04T13:27:16.753 回答
4

您是否考虑过用化学标记语言表达您的化学式?它非常通用,并且有很多工具/查看器可以将这些化学公式或化合物呈现为 2D 到 3D。

于 2010-06-04T14:21:55.403 回答
3

我正在开发一个需要对化学公式进行摩尔质量计算的程序,因此我创建了一个适用于各种公式的解决方案。

例如,“(CH3)16(Tc(H2O)3CO(BrFe3(ReCl)3(SO4)2)2)2MnO4”将导致“16C 48H 2Tc 12H 6O 2C 2O 4Br 12Fe 12Re 12Cl 8S 32O Mn 4O”(这种化合物是合成的,但是,嘿,它有效!)

这段代码是用 C# 编写的,所以我没有发布它。如果你有兴趣我可以发给你。在注意到 java 标签之前,我实际上写了一个完整的答案。

无论如何,它的工作原理基本上是递归地对括号匹配的原子块进行分组。它不处理诸如 2Pb 之类的系数(但 (Pb)2 或 Pb2 确实有效)或带电化合物(例如 OH-)。

绝不是简单或优雅的。我确实想要一个可行的解决方案,所以我知道有更好的方法(我什至从未尝试过正则表达式!)。但它适用于我需要的公式,也许它也适合你的。

这是我运行它的一些测试用例。看看它们,让我知道 C# 代码是否仍然对您有用。格式为(输入,预期输出)

        ("Pb ", " Pb"); 
        ("H ", " H"); 
        ("Pb2 ", " 2Pb"); 
        ("H2 ", " 2H");             
        ("3Pb2 ", " 6Pb");
        ("Pb2SO4", " 2Pb S 4O");                                     
        ("PbH2 ", " Pb 2H");            
        ("(PbH2)2 ", " 2Pb 4H");
        ("(CCC)2 ", " 2C 2C 2C");
        ("Pb(H2)2 ", " Pb 4H");            
        ("(Pb(H2)2)2 ", " 2Pb 8H"); 
        ("(Pb(H2)2)2NO3 ", " 2Pb 8H N 3O"); 
        ("(Ag(Pb(H2)2)2)2SO4 ", " 2Ag 4Pb 16H S 4O");             
        ("Pb(CH3(CH2)2CH3)2", " Pb 2C 6H 4C 8H 2C 6H"); 
        ("Na2(CH3(CH2)2CH3)2", " 2Na 2C 6H 4C 8H 2C 6H");
        ("Tc(H2O)3Fe3(SO4)2", " Tc 6H 3O 3Fe 2S 8O");
        ("Tc(H2O)3(Fe3(SO4)2)2", " Tc 6H 3O 6Fe 4S 16O");
        ("(Tc(H2O)3(Fe3(SO4)2)2)2", " 2Tc 12H 6O 12Fe 8S 32O");
        ("(Tc(H2O)3CO(Fe3(SO4)2)2)2", " 2Tc 12H 6O 2C 2O 12Fe 8S 32O");
        ("(Tc(H2O)3CO(BrFe3(ReCl)3(SO4)2)2)2MnO4", " 2Tc 12H 6O 2C 2O 4Br 12Fe 12Re 12Cl 8S 32O Mn 4O");
        ("(CH3)16(Tc(H2O)3CO(BrFe3(ReCl)3(SO4)2)2)2MnO4", " 16C 48H 2Tc 12H 6O 2C 2O 4Br 12Fe 12Re 12Cl 8S 32O Mn 4O"); 
于 2010-06-04T15:36:07.220 回答