我正在尝试编写一个C#化学式解析器,它从字符串输入中提取化学式。我已经弄清楚如何使用不含括号的化学公式(如H2O等)来做到这一点。但是,我不知道如何使用括号来完成这项工作,例如使用像Al2(HPO4)3这样的公式。
只是一个注释,但这会输出一个名为“ FormulaComponents ”的类列表,它们有两个变量,一个元素(字符串)和一个数字。
有什么想法吗?
编辑:这是我目前的尝试。它处理的所有内容都不包括括号。public static Formula Parse(string input)
{
var components = new List<FormulaComponent>();
const string elementRegex = "([A-Z][a-z]*)([0-9]*)";
const string validateRegex = "^(" + elementRegex + ")+$";
if (!Regex.IsMatch(input, validateRegex))
throw new FormatException("Input string was in an incorrect format.");
foreach (Match match in Regex.Matches(input, elementRegex))
{
var name = match.Groups[1].Value;
var count = match.Groups[2].Value != "" ?
int.Parse(match.Groups[2].Value) :
1;
if (ElementManager.FindElementBySymbol(name) == null)
throw new FormatException(name + " is not recognized as a valid element symbol.");
components.Add(new FormulaComponent { Element = ElementManager.FindElementBySymbol(name), Quantity = count });
}
return new Formula { Components = components };
}
答案 0 :(得分:1)
也许这是一种矫枉过正,但至少它是干净的 - 你可以使用lexer + parser来完成这项工作。
Lexer规则:
/[A-Z][a-z]*/ -> ATOM;
/[0-9]+/ -> NUM, Convert.ToInt32($text);
"(" -> LPAREN;
")" -> RPAREN;
解析器规则:
s -> c:comp { c };
atom -> a:ATOM { new Atom(a,1) }
| a:ATOM n:NUM { new Atom(a,n) }
;
comp -> LPAREN c:comp RPAREN n:NUM { new Compound(c,n) }
| c:comp+ { new Compounds(c) }
| a:atom { a }
;
这些只是规则(我没有在这里测试任何东西)。如果你愿意,你可以使用我的NLT lexer +解析器,但C#还有很多其他类似的工具 - 选择你喜欢的。
由于你没有嵌套的括号,你可能更容易使用正则表达式。
答案 1 :(得分:1)
由于我不知道您的Formula类是什么样的,我将结果放入MessageBox
public static Double getElements(String _molecule)
{
Boolean useParenthesis = Regex.IsMatch(_molecule, @"[A-Z][a-z]?\d*\((([A-Z][a-z]?\d*){1,2})\)\d*");
var findMatches = Regex.Matches(_molecule, @"\(?[A-Z][a-z]?\d*\)?"); // Get all elements
if (useParenthesis)
{
Double endNumber = Double.Parse(Regex.IsMatch(_molecule, @"\)\d+") ? Regex.Match(_molecule, @"\)\d+").Value.Remove(0, 1) : "1"); // Finds the number after the ')'
foreach (Match i in findMatches)
{
String element = Regex.Match(i.Value, "[A-Z][a-z]?").Value; // Gets the element
Double amountOfElement = 0;
if (Regex.IsMatch(i.Value, @"[\(\)]"))
{
if (!Double.TryParse(Regex.Replace(i.Value, @"(\(|\)|[A-Z]|[a-z])", ""), out amountOfElement))
amountOfElement = endNumber; // If the element has either '(' or ')' and doesn't specify an amount, then set it equal to the endnumber
else
amountOfElement *= endNumber; // If the element has either '(' or ')' and specifies an amount, then multiply it by the end number
}
else
amountOfElement = Double.Parse(String.IsNullOrWhiteSpace(i.Value.Replace(element, "")) ? "1" : i.Value.Replace(element, ""));
MessageBox.Show(element + " - " + amountOfElement);
}
return endNumber;
}
else
return 0.0;
}
答案 2 :(得分:0)
好吧,你可以使用这个正则表达式:
`"(([A-Z][a-z]*)([0-9]*)) |
((\()?([A-Z][a-z]*)([0-9]*)(\)[0-9]*)?)"`
匹配H2O或(HPO4)3。
获得匹配后,您可以解析尾随数字(如果有),并在括号中的部分再次运行正则表达式。类似的东西:
foreach (var match in regex.Matches(line))
{
if (match.Value[0] == '(')
{
// get the number from the end
var multiplier = match.Groups[whatever]; // whatever group index that is
// get the formula inside parentheses
var formula = match.Groups[formulaIndex]; // again, whatever group index
foreach (var match2 in regex.Matches(formula))
{
// parse that as a normal formula (i.e. not in parentheses)
// remember to multiply by your multiplier
}
}
else
{
// parse it as a normal formula
}
}
您可能希望将parse as a normal formula
作为单独的方法,以免您重复代码。或者,您可能会使解析位递归,以便内部foreach
循环再次调用该方法。如果你需要处理嵌套的括号,你几乎肯定必须这样做。