Pregunta

Estoy intentando analizar una fórmula química (en el formato, por ejemplo: Al2O3 o O3 o C o C11H22O12) en C # de una cadena. Funciona bien a menos que haya un solo átomo de un elemento particular (por ejemplo, el átomo de oxígeno en H2O). ¿Cómo puedo solucionar ese problema, y, además, es que hay una mejor manera de analizar una cadena fórmula química de lo que estoy haciendo?

ChemicalElement es una clase que representa un elemento químico. Tiene propiedades AtomicNumber (int), nombre (cadena), Símbolo (cadena). ChemicalFormulaComponent es una clase que representa un elemento químico y el recuento de átomo (por ejemplo, parte de una fórmula). Tiene propiedades Element (ChemicalElement), AtomCount (int).

El resto debe ser lo suficientemente claro para entender (espero) pero por favor, que me haga saber con un comentario si puedo aclarar cualquier cosa, antes de contestar.

Aquí está mi código actual:

    /// <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;
    }
¿Fue útil?

Solución

Me volvió a escribir su analizador utilizando expresiones regulares. Las expresiones regulares se adaptan a la perfección a lo que está haciendo. Espero que esto ayude.

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;
}

Otros consejos

El problema con el método es aquí:

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

Cuando usted no tiene un número, tampón conteo será 0, creo que este trabajo

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

Esto funciona cuando por fórmulas como HO2 o algo por el estilo. Creo que su método nunca se insertará en la colección formula el elemento de las de la fórmula química.

Usted debe añadir el último elemento del bufer de la colección antes de devolver el resultado, como esto:

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

    return formula;
}

en primer lugar: No he utilizado un generador de analizadores sintácticos en .NET, pero estoy bastante seguro de que podría encontrar algo adecuado. Esto permitirá escribir la gramática de fórmulas químicas en una forma mucho más legible. Véase, por ejemplo esta pregunta para una primera apertura.

Si desea mantener su enfoque: ¿Es posible que usted no añada su último elemento, no importa si tiene un número o no? Es posible que desee ejecutar el bucle con i<= chemicalFormula.Length y en caso de i==chemicalFormula.Length también añadir lo que tiene a su fórmula. A continuación, también se tiene que quitar su condición if (countBuffer > 0) porque countBuffer en realidad puede ser cero!

Regex debería funcionar bien con la fórmula simple, si desea dividir algo como:

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

podría ser más fácil de utilizar el analizador para que (debido a la anidación de compuesto). Cualquier programa de análisis debe ser capaz de manejarlo.

manchado este problema hace unos días pensé que sería buen ejemplo de cómo se puede escribir la gramática de un programa de análisis, por lo que he incluido gramática simple fórmula química en mi NLT suite. La clave reglas son - para lexer:

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

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

y por parser:

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)) }
      ;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top