我正在尝试从字符串中解析C#中的化学公式(格式,例如:Al2O3
或O3
或C
或C11H22O12
)。它工作正常,除非只有一个特定元素的原子(例如H2O
中的氧原子)。我该如何解决这个问题呢?此外,还有一种更好的解析化学式公式字符串的方法吗?
ChemicalElement是一个代表化学元素的类。它具有AtomicNumber(int),Name(字符串),Symbol(字符串)属性。 ChemicalFormulaComponent是表示化学元素和原子计数的类(例如,公式的一部分)。它具有属性Element(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;
}
答案 0 :(得分:10)
我使用正则表达式重写了你的解析器。正则表达式完全符合您正在做的事情。希望这会有所帮助。
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;
}
答案 1 :(得分:2)
您的方法存在问题:
// 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;
}
答案 2 :(得分:1)
首先:我没有在.net中使用过解析器生成器,但我很确定你能找到合适的东西。这将允许您以更易读的形式编写化学式的语法。例如,请参见this question作为第一次开始。
如果你想保留你的方法:你是否有可能不添加你的最后一个元素,无论它是否有数字?您可能希望使用i<= chemicalFormula.Length
运行循环,如果i==chemicalFormula.Length
也添加您的公式。然后您还必须删除if (countBuffer > 0)
条件,因为countBuffer实际上可能为零!
答案 3 :(得分:0)
如果你想分割类似的东西,正则表达式可以用简单的公式工作:
(Zn2(Ca(BrO4))K(Pb)2Rb)3
使用解析器可能更容易(因为复合嵌套)。任何解析器都应该能够处理它。
前几天我发现了这个问题,我认为如何为解析器编写语法是个很好的例子,所以我将简单的化学式语法包含在我的NLT套件中。 键规则是 - 对于词法分析器:
"(" -> 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)) }
;