Frage

Ich versuche, eine chemische Formel (im Format, zum Beispiel: Al2O3 oder O3 oder C oder C11H22O12) zu analysieren, in C # aus einem String. Es funktioniert gut, es sei denn, es gibt nur ein Atom von einem bestimmten Element (z.B. das Sauerstoffatom in H2O) ist. Wie kann ich das beheben dieses Problem, und darüber hinaus gibt es einen besseren Weg, um eine chemische Formel Zeichenfolge zu analysieren, als ich es tue?

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

Der Rest sollte klar genug sein, um zu verstehen (hoffe ich), aber bitte lassen Sie mich mit einem Kommentar, ob ich etwas klarstellen 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;
    }
War es hilfreich?

Lösung

schreibe ich Ihren Parser mit regulären Ausdrücken. Reguläre Ausdrücke passen die Rechnung perfekt für das, 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;
}

Andere Tipps

Das Problem mit Ihrer Methode ist hier:

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

Wenn Sie keine Nummer haben, wird Zählerpuffer 0 sein, ich glaube, dies wird Arbeit

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

Dies funktioniert, wenn Sie Formeln wie HO2 oder so ähnlich. Ich glaube, dass Ihre Methode wird nie das las Element der chemischen Formel in die formula Sammlung einfügen.

Sie sollten das letzte Element der Bufer der Sammlung vor Rückkehr das Ergebnis hinzuzufügen, wie folgt aus:

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

    return formula;
}

Regex sollte gut mit einfacher Formel arbeiten, wenn Sie etwas teilen mögen 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 soll den Umgang mit der Lage sein.

Ich entdeckte dieses Problem vor wenigen Tagen dachte ich, es gutes Beispiel wäre wie eine Grammatik für einen Parser schreiben kann, so dass ich einfache chemische Formel Grammatik enthalten in mein NLT Suite. Die Taste 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)) }
      ;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top