2010-11-07 4 views
8

Ich versuche, eine chemische Formel (in dem Format, zum Beispiel: Al2O3 oder O3 oder C oder C11H22O12) in C# von einer Zeichenfolge zu analysieren. Es funktioniert gut, es sei denn, es gibt nur ein Atom eines bestimmten Elements (z. B. das Sauerstoffatom in H2O). Wie kann ich dieses Problem beheben, und gibt es außerdem eine bessere Methode zum Analysieren einer chemischen Formelzeichenfolge als das, was ich tue?Analysieren einer chemischen Formel aus einer Zeichenfolge in C#?

ChemicalElement ist eine Klasse, die ein chemisches Element darstellt. Es hat Eigenschaften AtomicNumber (int), Name (string), Symbol (string). ChemicalFormulaComponent ist eine Klasse, die ein chemisches Element und eine Atomzahl darstellt (z. B. Teil einer Formel). Es hat Eigenschaften Element (ChemicalElement), AtomCount (int).

Der Rest sollte klar genug sein, um zu verstehen (ich hoffe), aber bitte lassen Sie mich mit einem Kommentar wissen, wenn ich etwas klären kann, bevor Sie antworten.

Hier ist mein aktueller Code:

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

Warum Prüfen Sie, ob das erste Zeichen der Formel bei jeder Iteration des 'for' oberen Fall ist Schleife ('! char.IsUpper (chemicalFormula, 0)')? Der Index hier ist immer "0". –

+0

Ich denke, deine Funktion hat auch Probleme mit etwas wie C4O2 ist das wahr? –

+0

Siehe auch die Seite http://stackoverflow.com/questions/2974362/parsing-a-chemical-formula/3742985. Es fragt nach einem in Java mit einer Antwort in Python und Links zu komplexeren ANTLR- und Python-Lösungen. –

Antwort

10

Ich schrieb Ihren Parser mit regulären Ausdrücken. Reguläre Ausdrücke passen perfekt zu dem, was Sie tun. Hoffe das hilft.

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

Das sieht perfekt aus, vielen Dank. Randnotiz - sollte nicht das * in der Nähe von [A-Z] [a-z] ein + sein? –

+0

Das '*' gilt nur für eine '[] 'Gruppe. Dies bedeutet, dass das '[A-Z]' genau einmal erscheinen muss (weil es kein '*' oder ein '+' hat), und das '[a-z]' muss null oder mehrmals erscheinen. –

+0

Ach ja, natürlich. Ich lese meine Klammern nicht richtig. Danke noch einmal! –

2

Das Problem mit Ihrer Methode ist hier:

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

Wenn Sie Puffer 0 keine Nummer haben, zählen wird, ich denke, das

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

Diese Arbeit wird wird funktionieren, wenn für Formeln wie HO2 oder so ähnlich. Ich glaube, dass Ihre Methode wird niemals in die formula Sammlung das Element der chemischen Formel einfügen.

Sie sollten das letzte Element des Bufer zur Sammlung hinzufügen, bevor das Ergebnis zurück, wie folgt aus:

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

    return formula; 
} 
1

allererst: Ich habe nicht einen Parser-Generator in .net verwendet, aber ich Ich bin mir ziemlich sicher, dass du etwas passendes finden kannst. Dies würde Ihnen erlauben, die Grammatik der chemischen Formeln in einer viel besser lesbaren Form zu schreiben. Sehen Sie zum Beispiel this question für einen ersten Start.

Wenn Sie Ihren Ansatz beibehalten möchten: Ist es möglich, dass Sie Ihr letztes Element nicht hinzufügen, egal ob es eine Nummer hat oder nicht? Vielleicht möchten Sie Ihre Schleife mit i<= chemicalFormula.Length laufen lassen und im Fall von i==chemicalFormula.Length auch hinzufügen, was Sie zu Ihrer Formel haben. Sie müssen dann auch Ihre if (countBuffer > 0) Bedingung entfernen, da countBuffer tatsächlich Null sein kann!

0

Regex sollte mit einfachen Formel funktionieren, wenn Sie etwas teilen möchten wie:

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

könnte es einfacher sein, den Parser für sie zu verwenden (wegen der Verbindung Verschachtelung). Jeder Parser sollte in der Lage sein, damit umzugehen.

Ich entdeckte dieses Problem vor ein paar Tagen Ich dachte, es wäre ein gutes Beispiel, wie man Grammatik für einen Parser schreiben kann, also habe ich einfache chemische Formel Grammatik in meine NLT Suite.Die Schlüssel Regeln sind - für Lexer:

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

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

und für 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)) } 
     ;