2010-11-07 20 views
8

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

ChemicalElement es una clase que representa un elemento químico. Tiene propiedades AtomicNumber (int), Name (string), Symbol (string). ChemicalFormulaComponent es una clase que representa un elemento químico y un recuento de átomos (por ejemplo, parte de una fórmula). Tiene propiedades Element (ChemicalElement), AtomCount (int).

El resto debe ser lo suficientemente claro como para entenderlo (espero) pero hágamelo saber con un comentario si puedo aclarar algo, 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; 
    } 
+0

¿Por qué estás mirando si el primer carácter de la fórmula es mayúscula en cada iteración del 'for' loop ('! char.IsUpper (chemicalFormula, 0)')? El índice aquí siempre es '0'. –

+0

Creo que su función también tiene problemas con algo como C4O2 ¿es esto cierto? –

+0

Vea también la página http://stackoverflow.com/questions/2974362/parsing-a-chemical-formula/3742985. Pide uno en Java, con una respuesta en Python, y enlaces a soluciones ANTLR y Python más complejas. –

Respuesta

10

Reescribí su analizador utilizando expresiones regulares. Las expresiones regulares se ajustan perfectamente a lo que estás 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; 
} 
+0

Esto se ve perfecto, muchas gracias. Sidenote sin embargo, ¿no debería el * cerca de [A-Z] [a-z] ser un +? –

+0

El '*' solo se aplica a un grupo '[]'. Esto significa que '[A-Z]' debe aparecer exactamente una vez (porque no tiene un '*' o un '+'), y '[a-z]' debe aparecer cero o más veces. –

+0

Ah sí, por supuesto. No leo mis paréntesis correctamente. ¡Gracias de nuevo! –

2

El problema con el método está aquí:

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

Cuando usted no tiene un número, contar búfer será 0, creo que esto va a funcionar

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

Este funcionará cuando se utilicen fórmulas como HO2 o algo así. Creo que su método nunca insertará en la colección formula el último elemento de la fórmula química.

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

en primer lugar: No he utilizado un generador de analizadores sintácticos en .NET, pero me Estoy bastante seguro de que podrías encontrar algo apropiado. Esto le permitiría escribir la gramática de fórmulas químicas en una forma mucho más legible. Ver por ejemplo this question para un primer inicio.

Si desea mantener su enfoque: ¿Es posible que no agregue su último elemento sin importar si tiene un número o no? Es posible que desee ejecutar su ciclo con i<= chemicalFormula.Length y en el caso de i==chemicalFormula.Length también agregue lo que tiene a su fórmula. También debe eliminar su condición if (countBuffer > 0) porque countBuffer puede ser cero.

0

expresión regular 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 ello (a causa de anidación compuesto). Cualquier analizador debería ser capaz de manejarlo.

Vi este problema hace unos días, pensé que sería un buen ejemplo de cómo se puede escribir gramática para un analizador sintáctico, así que incluí la gramática de fórmula química simple en mi suite NLT.Los 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)) } 
     ; 
Cuestiones relacionadas