Расположение химической формулы из строки в C#?
Вопрос
Я пытаюсь проанализировать химическую формулу (например, в формате: Al2O3
или O3
или C
или C11H22O12
) в C# из строки. Он работает нормально, если нет только одного атома определенного элемента (например, атом кислорода в H2O
). Как я могу решить эту проблему, и, кроме того, есть ли лучший способ проанализировать строку химической формулы, чем у меня?
ChemicalElement - это класс, представляющий химический элемент. Он имеет свойства AtomicNumber (int), имя (строка), символ (строка). Chemicalformulacomponent - это класс, представляющий химический элемент и количество атомов (например, часть формулы). У него есть элемент свойств (ChemicalElement), Atomcount (int).
Остальное должно быть достаточно ясным, чтобы понять (я надеюсь), но, пожалуйста, дайте мне знать с комментарием, если я смогу что -нибудь уточнить, прежде чем вы ответите.
Вот мой текущий код:
/// <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;
}
Решение
Я переписал ваш анализатор, используя регулярные выражения. Регулярные выражения идеально подходят для того, что вы делаете. Надеюсь это поможет.
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;
}
Другие советы
Проблема с вашим методом здесь:
// Add the chemical element and its atom count
if (countBuffer > 0)
Когда у вас нет числа, буфер графа будет 0, я думаю, что это сработает
// Add the chemical element and its atom count
if (countBuffer > 0 || nameBuffer != String.Empty)
Это будет работать, когда для формул, как HO2 или что -то в этом роде. Я считаю, что ваш метод никогда не встанет в formula
Сборник LAS -элемент химической формулы.
Вы должны добавить последний элемент Bufer в коллекцию, прежде чем вернуть результат, например:
formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(nameBuffer), countBuffer));
return formula;
}
Прежде всего: я не использовал генератор анализатора в .NET, но я уверен, что вы сможете найти что -то подходящее. Это позволило бы вам написать грамматику химических формул в гораздо более читаемой форме. Смотрите, например этот вопрос для первого начала.
Если вы хотите сохранить свой подход: возможно ли, что вы не добавляете свой последний элемент, независимо от того, есть ли в нем номер или нет? Вы можете запустить свою петлю с помощью i<= chemicalFormula.Length
и в случае i==chemicalFormula.Length
Также добавьте то, что у вас есть в свою формулу. Затем вам также придется удалить свой if (countBuffer > 0)
Условие, потому что графбаффер на самом деле может быть нулевым!
Regex должна работать с простой формулой, если вы хотите разделить что -то вроде:
(Zn2(Ca(BrO4))K(Pb)2Rb)3
Это может быть проще использовать анализатор для него (из -за гнездования соединения). Любой анализатор должен быть способен обработать его.
Я заметил эту проблему несколько дней назад, я подумал, что это был бы хороший пример, как можно написать грамматику для анализатора, поэтому я включил в свою грамматику простой химической формулы в свою Норм набор. То ключ Правила - для Лексера:
"(" -> LPAREN;
")" -> RPAREN;
/[0-9]+/ -> NUM, Convert.ToInt32($text);
/[A-Z][a-z]*/ -> ATOM;
и для анализатора:
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)) }
;