Разбор химической формулы из строки в C #? - PullRequest
8 голосов
/ 07 ноября 2010

Я пытаюсь разобрать химическую формулу (в формате, например: Al2O3 или O3 или C или C11H22O12) в C # из строки.Он работает нормально, если только нет одного атома конкретного элемента (например, атом кислорода в H2O).Как я могу решить эту проблему, и, кроме того, есть ли лучший способ анализа строки химической формулы, чем я делаю?

ChemicalElement - это класс, представляющий химический элемент.Он имеет свойства AtomicNumber (int), Name (строка), Symbol (строка).ChemicalFormulaComponent - это класс, представляющий химический элемент и количество атомов (например, часть формулы).У него есть свойства Element (ChemicalElement), AtomCount (int).

Остальное должно быть достаточно ясным, чтобы понять (я надеюсь), но, пожалуйста, дайте мне знать с комментарием, если я могу что-то уточнить, прежде чем ответить.1010 *

Вот мой текущий код:

    /// <summary>
    /// Parses a chemical formula from a string.
    /// </summary>
    /// <param name="chemicalFormula">The string to parse.</param>
    /// <exception cref="FormatException">The chemical formula was in an invalid format.</exception>
    public static Collection<ChemicalFormulaComponent> FormulaFromString(string chemicalFormula)
    {
        Collection<ChemicalFormulaComponent> formula = new Collection<ChemicalFormulaComponent>();

        string nameBuffer = string.Empty;
        int countBuffer = 0;

        for (int i = 0; i < chemicalFormula.Length; i++)
        {
            char c = chemicalFormula[i];

            if (!char.IsLetterOrDigit(c) || !char.IsUpper(chemicalFormula, 0))
            {
                throw new FormatException("Input string was in an incorrect format.");
            }
            else if (char.IsUpper(c))
            {
                // Add the chemical element and its atom count
                if (countBuffer > 0)
                {
                    formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(nameBuffer), countBuffer));

                    // Reset
                    nameBuffer = string.Empty;
                    countBuffer = 0;
                }

                nameBuffer += c;
            }
            else if (char.IsLower(c))
            {
                nameBuffer += c;
            }
            else if (char.IsDigit(c))
            {
                if (countBuffer == 0)
                {
                    countBuffer = c - '0';
                }
                else
                {
                    countBuffer = (countBuffer * 10) + (c - '0');
                }
            }
        }

        return formula;
    }

Ответы [ 4 ]

10 голосов
/ 07 ноября 2010

Я переписал ваш парсер с помощью регулярных выражений. Регулярные выражения идеально подходят для того, что вы делаете. Надеюсь, это поможет.

public static void Main(string[] args)
{
    var testCases = new List<string>
    {
        "C11H22O12",
        "Al2O3",
        "O3",
        "C",
        "H2O"
    };

    foreach (string testCase in testCases)
    {
        Console.WriteLine("Testing {0}", testCase);

        var formula = FormulaFromString(testCase);

        foreach (var element in formula)
        {
            Console.WriteLine("{0} : {1}", element.Element, element.Count);
        }
        Console.WriteLine();
    }

    /* Produced the following output

    Testing C11H22O12
    C : 11
    H : 22
    O : 12

    Testing Al2O3
    Al : 2
    O : 3

    Testing O3
    O : 3

    Testing C
    C : 1

    Testing H2O
    H : 2
    O : 1
        */
}

private static Collection<ChemicalFormulaComponent> FormulaFromString(string chemicalFormula)
{
    Collection<ChemicalFormulaComponent> formula = new Collection<ChemicalFormulaComponent>();
    string elementRegex = "([A-Z][a-z]*)([0-9]*)";
    string validateRegex = "^(" + elementRegex + ")+$";

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

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

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

        formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(name), count));
    }

    return formula;
}
2 голосов
/ 07 ноября 2010

Проблема с вашим методом здесь:

            // Add the chemical element and its atom count
            if (countBuffer > 0)

Если у вас нет числа, буфер подсчета будет равен 0, я думаю, это будет работать

            // Add the chemical element and its atom count
            if (countBuffer > 0 || nameBuffer != String.Empty)

Этобудет работать, когда для формул, таких как HO2 или что-то подобное.Я считаю, что ваш метод никогда не вставит в коллекцию formula элемент las химической формулы.

Вы должны добавить последний элемент буфера в коллекцию, прежде чем возвращать результат, например:

    formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(nameBuffer), countBuffer));

    return formula;
}
1 голос
/ 07 ноября 2010

Прежде всего: я не использовал генератор парсера в .net, но я уверен, что вы могли бы найти что-то подходящее. Это позволит вам написать грамматику химических формул в гораздо более удобочитаемой форме. См. Например этот вопрос для первого запуска.

Если вы хотите сохранить свой подход: возможно ли, что вы не добавите свой последний элемент, независимо от того, есть ли у него номер или нет? Возможно, вы захотите запустить цикл с i<= chemicalFormula.Length, а в случае i==chemicalFormula.Length также добавьте то, что у вас есть, в вашу формулу. Затем вам также нужно удалить условие if (countBuffer > 0), потому что countBuffer может фактически быть нулевым!

0 голосов
/ 13 ноября 2013

Регулярное выражение должно работать с простой формулой, если вы хотите разделить что-то вроде:

(Zn2(Ca(BrO4))K(Pb)2Rb)3

для него может быть проще использовать парсер (из-за сложного вложения). Любой парсер должен быть в состоянии справиться с этим.

Я обнаружил эту проблему несколько дней назад и подумал, что это будет хорошим примером того, как можно написать грамматику для анализатора, поэтому я включил грамматику простой химической формулы в свой набор NLT . Правила key - для лексера:

"(" -> LPAREN;
")" -> RPAREN;

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

и для парсера:

comp -> e:elem { e };

elem -> LPAREN e:elem RPAREN n:NUM? { new Element(e,$(n : 1)) }
      | e:elem++ { new Element(e,1) }
      | a:ATOM n:NUM? { new Element(a,$(n : 1)) }
      ;
...