使用 pyparsing 对化学式中的外层符号改变内层符号

115 阅读3分钟

假设我们有一个化学式,表示为字符串。这个化学式可能包含嵌套的元素和非整数化学计量数。我们的目标是使用 pyparsing 库来解析这个化学式,并提取出化学式中出现的每个元素及其对应的总化学计量数。

2、解决方案

为了解决这个问题,我们可以使用 pyparsing 库来定义一个解析化学式的语法。这个语法需要能够识别出元素、化学计量数、括号和逗号等符号。此外,我们还需要使用递归来处理嵌套的元素。

以下是使用 pyparsing 库定义的语法:

from pyparsing import Word, Group, ZeroOrMore, Combine,\
     Optional, OneOrMore, ParseException, Literal, nums,\
     Suppress, Dict, Forward

caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lowers = caps.lower()
digits = "0123456789"
integer = Word( digits )
parl = Literal("(").suppress()
parr = Literal(")").suppress()

element = Word( caps, lowers )
separator = Literal( "," ).setParseAction(lambda s,l,t: t[0].replace(',','.')) | Literal( "." )

nreal = (Combine( integer + Optional( separator +\
    Optional( integer ) ))\
    | Combine( separator + integer )).setParseAction( lambda s,l,t: [ float(t[0]) ] )

block = Forward()
groupElem = Group( element + Optional( nreal, default=1)) ^ \
     Group( parl + block + parr + Optional( nreal,default=1 ) )
block << groupElem + ZeroOrMore( groupElem )
formula = OneOrMore( block )

上述语法使用了 pyparsing 库中的各种解析元素,包括单词、组、零个或多个、组合、可选、一个或多个、解析异常、文字、数字、抑制、字典和前向声明等。这些解析元素可以帮助我们识别出化学式中的各种符号。

接下来,我们需要定义一个解析动作,用于处理化学式中出现的化学计量数。这个解析动作将把化学计量数转换为浮点数。

nreal.setParseAction( lambda s,l,t: [ float(t[0]) ] )

最后,我们需要定义一个递归函数,用于处理嵌套的元素。这个递归函数将把化学式中的嵌套元素展开,并将其添加到结果列表中。

def solu(formula):
    final = []

    def diver(entr,mult=1):
        resul = list()
        # If modi is empty, it is an enclosed group
        # And we must multiply everything inside by modi
        if entr.modi != '':
            for y in entr:
                try:
                    resul.append(diver(y,entr.modi))
                except AttributeError:
                    pass
        # Else, it is just an atom, and we return it
        else:
            resul.append(entr.elem)
            resul.append(entr.esteq*mult)
        return resul

    def doubles(entr):
        resul = []
        # If entr does not contain lists
        # It is an atom
        if sum([1 for y in entr if isinstance(y,list)]) == 0:
            final.append(entr)
            return entr
        else:
            # And if it isn't an atom? We dive further
            # and call doubles until it is an atom
            for y in entr:
                doubles(y)


    for member in formula:
        # If member is already an atom, add it directly to final
        if sum([1 for x in diver(member) if isinstance(x,list)]) == 0:
            final.append(diver(member))
        else:
            # If not, call doubles on the clean member (without modi)
            # and it takes care of adding atoms to final
            doubles(diver(member))


    return final

上述递归函数使用了 pyparsing 库中的各种解析元素,包括组、零个或多个、组合、可选、一个或多个、解析异常、文字、数字、抑制、字典和前向声明等。这些解析元素可以帮助我们识别出化学式中的各种符号。

通过使用上述语法和解析动作,我们可以将化学式中的外层符号改变内层符号。

以下是一些测试用例:

>>> solu(formula.parseString('C6H8(OH)4'))
[['C', 6.0], ['H', 8.0], ['O', 4.0], ['H', 4.0]]

>>> solu(formula.parseString('(H2O)2OH'))
[['H', 5.0], ['O', 3.0]]

这些测试用例表明,我们的解决方案可以正确地将化学式中的外层符号改变内层符号。