C#用父母解析化学式

时间:2013-11-10 01:05:05

标签: c# .net parsing

我正在尝试编写一个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 };
}

3 个答案:

答案 0 :(得分:1)

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

Lexer规则:

/[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 +解析器,但C#还有很多其他类似的工具 - 选择你喜欢的。

由于你没有嵌套的括号,你可能更容易使用正则表达式。

答案 1 :(得分: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;
    }

答案 2 :(得分: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循环再次调用该方法。如果你需要处理嵌套的括号,你几乎肯定必须这样做。