Parsen eine chemische Formel von einem String in C #?
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;
}
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;
}
allererst: Ich habe nicht einen Parser-Generator in .net verwendet, aber ich bin ziemlich sicher, könnten Sie etwas Passendes finden. Dies würde es ermöglichen Ihnen die Grammatik der chemischen Formeln in einer weitaus lesbarer Form zu schreiben. Siehe zum Beispiel für einen ersten Start href="https://stackoverflow.com/questions/153572/parser-generator-that-outputs-c-given-a-bnf-grammar">.
Wenn Sie Ihren Ansatz behalten wollen: Ist es möglich, dass Sie nicht fügen Sie Ihr letztes Element unabhängig davon, ob es eine Nummer hat oder nicht? Vielleicht möchten Sie Ihre Schleife mit i<= chemicalFormula.Length
und bei i==chemicalFormula.Length
läuft auch hinzufügen, was Sie Ihre eigene Formel haben. Sie müssen dann auch Ihre if (countBuffer > 0)
Zustand entfernen, weil countBuffer tatsächlich Null sein kann!
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)) }
;