2010-11-07 23 views
8

Sto cercando di analizzare una formula chimica (nel formato, ad esempio: Al2O3 o O3 o C o C11H22O12) in C# da una stringa. Funziona bene a meno che non ci sia un solo atomo di un particolare elemento (ad esempio l'atomo di ossigeno in H2O). Come posso risolvere questo problema, e inoltre, c'è un modo migliore per analizzare una stringa di formula chimica di quello che sto facendo?Analizzare una formula chimica da una stringa in C#?

ChemicalElement è una classe che rappresenta un elemento chimico. Ha proprietà AtomicNumber (int), Name (stringa), Symbol (stringa). ChemicalFormulaComponent è una classe che rappresenta un elemento chimico e un conteggio di atomi (ad esempio una parte di una formula). Ha proprietà Element (ChemicalElement), AtomCount (int).

Il resto dovrebbe essere abbastanza chiaro per capire (spero) ma per favore fatemi sapere con un commento se posso chiarire qualsiasi cosa, prima di rispondere.

Ecco il mio codice corrente:

/// <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

Perché si sta verificando se il primo carattere della formula è maiuscolo su ogni iterazione del 'for' loop ('! char.IsUpper (chemicalFormula, 0)')? L'indice qui è sempre '0'. –

+0

Penso che la tua funzione abbia anche problemi con qualcosa come C4O2 è vero? –

+0

Vedere anche la pagina http://stackoverflow.com/questions/2974362/parsing-a-chemical-formula/3742985. Ne richiede uno in Java, con una risposta in Python e collegamenti a soluzioni ANTLR e Python più complesse. –

risposta

10

ho riscritto il parser utilizzando le espressioni regolari. Le espressioni regolari si adattano perfettamente al disegno di legge per quello che stai facendo. Spero che questo ti aiuti.

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

Questo sembra perfetto, grazie mille. Sidenote però - non dovrebbe * il vicino [A-Z] [a-z] essere un +? –

+0

Il '*' si applica solo a un gruppo '[]'. Ciò significa che '[A-Z]' deve apparire esattamente una volta (perché non ha un '*' o un '+'), e '[a-z]' deve apparire zero o più volte. –

+0

Ah sì, certo. Non leggendo correttamente la mia parentesi. Grazie ancora! –

2

Il problema con il vostro metodo è qui:

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

Quando non si dispone di un numero, conta di buffer sarà 0, penso che questo funzionerà

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

Questo funzionerà quando per formule come HO2 o qualcosa del genere. Credo che il tuo metodo non inserirà mai nella collezione formula l'elemento las della formula chimica.

Si dovrebbe aggiungere l'ultimo elemento della bufer alla raccolta prima di restituire il risultato, in questo modo:

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

    return formula; 
} 
1

prima di tutto: non ho usato un generatore di parser in .net, ma io Sono sicuro che potresti trovare qualcosa di appropriato. Ciò consentirebbe di scrivere la grammatica delle formule chimiche in una forma molto più leggibile. Si veda ad esempio this question per un primo avvio.

Se vuoi mantenere il tuo approccio: è possibile che tu non aggiunga il tuo ultimo elemento, non importa se ha un numero o no? Potresti voler eseguire il ciclo con i<= chemicalFormula.Length e nel caso di i==chemicalFormula.Length aggiungere anche quello che hai alla tua Formula. Devi anche rimuovere la tua condizione if (countBuffer > 0) perché countBuffer può effettivamente essere zero!

0

Regex dovrebbe funzionare bene con formula semplice, se si desidera dividere qualcosa di simile:

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

potrebbe essere più facile da usare il parser per esso (a causa di nidificazione composto). Qualsiasi parser dovrebbe essere in grado di gestirlo.

Ho individuato questo problema pochi giorni fa ho pensato che sarebbe stato un buon esempio di come si possa scrivere la grammatica per un parser, quindi ho incluso la semplice grammatica delle formule chimiche nella mia suite NLT.I chiave regole sono - per lexer:

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

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

e per 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)) } 
     ; 
Problemi correlati