用 Python 计算分子化合物中的元素数量(如果可能的话递归)?

Counting the number of elements in a molecular compound with Python (recursion if possible)?

所以我正在尝试编写一些代码来告诉我任何给定化合物中元素的数量。我什至不确定从哪里开始:我尝试编写一些代码,但后来意识到它只适用于简单的化合物(或者根本不起作用)。这是我想要的示例:

>>> function_input : 'NaMg3Al6(BO3)3Si6O18(OH)4', 'O'

>>> function_return : 31

到目前为止,我的代码一团糟(它不起作用,它只是说明了我粗略的思考过程):

def get_pos_from_count(string: str, letter: str):
    count = string.count(letter)
    lens = [-1]
    for i in range(count):
        lens += [string[lens[i] + 1:].index(letter) + lens[i] + 1]
    return lens[1:]



def find_number(string, letter):
    if string.count(letter) == 0: return 0
    numbers = '1234567890'
    try:
        mul1 = int(string[0])
    except ValueError:
        mul1 = 0
    mul2 = []
    sub_ = 1
    list_of_positions = get_pos_from_count(string, letter)
    for i in list_of_positions:
        try:
            sub_ += int(string[i + 1]) if string[i + 1] in numbers else 0
        except IndexError: pass
        if string[i + 1:].count(')') > string[i + 1].count('('):
            try:
                mul2 += int(string[string[i + 1:].count(')') + 1])
            except (IndexError, ValueError): pass
    return mul1 * sub_ * mul2

我尝试实施的方法是:

  1. 求出该元素在化合物中出现的次数。
  2. 找到每个下标,如果所述元素在括号内则乘以括号外的下标。
  3. 求和所有下标,乘以化合物的数量(字符串中的第一个字符)
  4. Return 向用户说号码

但后来我意识到我的代码要么非常长,要么需要递归,我不知道如何在这里应用。

如果可能的话,我想要一个半工作功能,但是关于如何处理这个问题的快速提示也很有帮助!

如果可能的话,我不想使用外部库。

tl;drThis question 用于元素的原子性,没有外部库(如果可能)。

编辑:是的,我链接的问题确实提示了如何执行此操作,但是当我尝试使任何代码仅适用于一个元素并设置它的权重时到 1,我 运行 遇到了很多我不知道要解决的问题。

试试这个递归函数:

import re

def find_number(s, e):
    if s == '':
        return 0
    x = re.search('\(([A-Za-z0-9]*)\)([0-9]*)', s)
    if x is not None:
        return (find_number(s[:x.start()] + s[x.end():], e) +
                find_number(x.groups()[0], e) * int(x.groups()[1] or '1'))
    return sum(int(x.groups()[0] or '1')
               for x in re.finditer(f'{e}([0-9]*)', s))

示例:

>>> find_number('NaMg3Al6(BO3)3Si6O18(OH)4', 'O')
31

让我们把任务分成三个部分:

  • 将字符串标记为元素、数字和括号的列表;
  • 解析括号以获得带子列表的嵌套列表;
  • 计算嵌套列表中的元素。

介绍我的工具:

from more_itertools import split_when, pairwise
from itertools import chain
from collections import Counter

def nest_brackets(tokens, i = 0):
    l = []
    while i < len(tokens):
        if tokens[i] == ')':
            return i,l
        elif tokens[i] == '(':
            i,subl = nest_brackets(tokens, i+1)
            l.append(subl)
        else:
            l.append(tokens[i])
        i += 1
    return i,l

def parse_compound(s):
    tokens = [''.join(t) for t in split_when(s, lambda a,b: b.isupper() or b in '()' or (b.isdigit() and not a.isdigit()))]
    tokens = [(int(t) if t.isdigit() else t) for t in tokens]
    i, l = nest_brackets(tokens)
    assert(i == len(tokens)) # crash if unmatched ')'
    return l

def count_elems(parsed_compound):
    c = Counter()
    for a,b in pairwise(chain(parsed_compound, (1,))):
        if not isinstance(a, int):
            subcounter = count_elems(a) if isinstance(a, list) else {a: 1}
            n = b if isinstance(b, int) else 1
            for elem,k in subcounter.items():
                c[elem] += k * n
    return c

s = 'NaMg3Al6(B(CO2)3)3Si6O18(OH)4'

l = parse_compound(s)
print(l)
# ['Na', 'Mg', 3, 'Al', 6, ['B', ['C', 'O', 2], 3], 3, 'Si', 6, 'O', 18, ['O', 'H'], 4]


c = count_elems(l)
print(c)
# Counter({'O': 40, 'C': 9, 'Al': 6, 'Si': 6, 'Mg': 3, 'B': 3, 'Na': 1})

print(c['O'])
# 40