-1

我正在尝试编写一个从字符串输入中提取化学式的 C# 化学式解析器。我已经想出了如何使用不包含括号的化学公式(例如 H2O 等)来做到这一点。但是,我不知道如何使用括号来完成这项工作,例如使用 Al2(HPO4)3 之类的公式。

只是一个注释,但这会输出一个名为“ FormulaComponents ”的类列表,它有两个变量,一个元素(字符串)和一个数字。

有任何想法吗?

编辑:这是我目前的尝试。它处理所有没有括号的东西。

public static Formula Parse(string input)
{
    var components = new List<FormulaComponent>();

    const string elementRegex = "([A-Z][a-z]*)([0-9]*)";
    const string validateRegex = "^(" + elementRegex + ")+$";

    if (!Regex.IsMatch(input, validateRegex))
        throw new FormatException("Input string was in an incorrect format.");

    foreach (Match match in Regex.Matches(input, elementRegex))
    {
        var name = match.Groups[1].Value;

        var count = match.Groups[2].Value != "" ?
            int.Parse(match.Groups[2].Value) :
            1;

        if (ElementManager.FindElementBySymbol(name) == null)
            throw new FormatException(name + " is not recognized as a valid element symbol.");

        components.Add(new FormulaComponent { Element = ElementManager.FindElementBySymbol(name), Quantity = count });
    }

    return new Formula { Components = components };
}
4

3 回答 3

1

由于我不知道您的 Formula 类是什么样的,所以我将结果放入 MessageBox

    public static Double getElements(String _molecule)
    {
        Boolean useParenthesis = Regex.IsMatch(_molecule, @"[A-Z][a-z]?\d*\((([A-Z][a-z]?\d*){1,2})\)\d*");
        var findMatches = Regex.Matches(_molecule, @"\(?[A-Z][a-z]?\d*\)?"); // Get all elements
        if (useParenthesis)
        {
            Double endNumber = Double.Parse(Regex.IsMatch(_molecule, @"\)\d+") ? Regex.Match(_molecule, @"\)\d+").Value.Remove(0, 1) : "1"); // Finds the number after the ')'
            foreach (Match i in findMatches)
            {
                String element = Regex.Match(i.Value, "[A-Z][a-z]?").Value; // Gets the element
                Double amountOfElement = 0;
                if (Regex.IsMatch(i.Value, @"[\(\)]"))
                {
                    if (!Double.TryParse(Regex.Replace(i.Value, @"(\(|\)|[A-Z]|[a-z])", ""), out amountOfElement))
                        amountOfElement = endNumber; // If the element has either '(' or ')' and doesn't specify an amount, then set it equal to the endnumber
                    else
                        amountOfElement *= endNumber; // If the element has either '(' or ')' and specifies an amount, then multiply it by the end number
                }
                else
                    amountOfElement = Double.Parse(String.IsNullOrWhiteSpace(i.Value.Replace(element, "")) ? "1" : i.Value.Replace(element, ""));
                MessageBox.Show(element + " - " + amountOfElement);
            }
            return endNumber;
        }
        else
            return 0.0;
    }
于 2014-11-20T03:22:40.623 回答
1

也许这是一种矫枉过正,但至少它是干净的——你可以使用 lexer+parser 来完成这项工作。

词法分析器规则:

/[A-Z][a-z]*/ -> ATOM;
/[0-9]+/ -> NUM, Convert.ToInt32($text);
"(" -> LPAREN;
")" -> RPAREN;

和解析器规则:

s -> c:comp { c };

atom -> a:ATOM { new Atom(a,1) }
      | a:ATOM n:NUM { new Atom(a,n) }
      ;

comp -> LPAREN c:comp RPAREN n:NUM { new Compound(c,n) }
      | c:comp+ { new Compounds(c) }
      | a:atom { a }
      ;

这些只是规则(我没有在这里测试任何东西)。如果你喜欢,你可以使用我的NLT lexer+parser,但是还有很多其他的 C# 工具——选择你最喜欢的。

由于您没有嵌套括号,因此正则表达式可能对您来说更容易。

于 2013-11-10T10:31:47.240 回答
0

好吧,你可以有这个正则表达式:

`"(([A-Z][a-z]*)([0-9]*)) |
  ((\()?([A-Z][a-z]*)([0-9]*)(\)[0-9]*)?)"`

这与 H2O 或 (HPO4)3 匹配。

当你得到你的匹配时,你可以解析出尾随数字(如果有的话),然后在括号中的部分上再次运行正则表达式。就像是:

foreach (var match in regex.Matches(line))
{
    if (match.Value[0] == '(')
    {
        // get the number from the end
        var multiplier = match.Groups[whatever];  // whatever group index that is
        // get the formula inside parentheses
        var formula = match.Groups[formulaIndex]; // again, whatever group index
        foreach (var match2 in regex.Matches(formula))
        {
            // parse that as a normal formula (i.e. not in parentheses)
            // remember to multiply by your multiplier
        }
    }
    else
    {
        // parse it as a normal formula
    }
}

您可能希望将其设为parse as a normal formula单独的方法,以免重复代码。或者,您可能会使解析位递归,以便内部foreach循环再次调用该方法。如果您需要处理嵌套括号,您几乎肯定必须这样做。

于 2014-11-20T05:42:01.840 回答