我必须编写一个程序,将用户的化学方程式作为输入,如12 CO2 + 6 H2O - > 2 C6H12O6 + 12 O2,并观察原子的数量是否在两个站点相同。有没有办法轻松计算和解析它?
例如:
<12> CO2 + 6H2O - > 2 C6H12O6 + 12O212 * 2 + 6 * 2 - &gt; 2 * 6 + 2 * 12 + 2 * 6 + 12 * 2
在这种情况下,应该输出&#34; false&#34;。
这是我的代码,但实际上只是尝试一下:
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
List<String> list = new ArrayList<String>();
String input = "";
while (!(input.equals("end"))) {
input = s.nextLine();
list.add(input);
}
list.remove(list.size() - 1);
for (int i = 0; i < list.size(); i++) {
int before = 0;
int after = 0;
String string = list.get(i);
string = besserUmwandeln(string);
System.out.println(string);
}
}
public static String besserUmwandeln(String string) {
string = string.replace("-", "");
string = string.trim().replaceAll(("\\s+"), " ");
string = string.replace(' ', '*');
StringBuilder builder = new StringBuilder(string);
System.out.println(string);
for (int k = 0; k < builder.length(); k++) {
if (Character.isUpperCase(builder.charAt(k))) {
builder.setCharAt(k, ':');
}
if (Character.isLowerCase(builder.charAt(k))) {
builder.setCharAt(k, '.');
}
if (Character.isDigit(builder.charAt(k))) {
} else {
}
}
for (int j = 0; j < builder.length(); j++) {
if (j < builder.length() && builder.charAt(j) == ':' && builder.charAt(j + 1) == '.') {
builder.deleteCharAt(j + 1);
}
}
for (int i = 0; i < builder.length(); i++) {
if (i < builder.length() - 1 && builder.charAt(i) == ':' && builder.charAt(i + 1) == ':') {
builder.deleteCharAt(i);
}
}
for (int i = 0; i < builder.length(); i++) {
if (i < builder.length() - 1 && builder.charAt(i) == '+' && builder.charAt(i + 1) == '*') {
builder.deleteCharAt(i + 1);
}
}
for (int i = 0; i < builder.length(); i++) {
if (i < builder.length() - 1 && builder.charAt(i) == '*' && builder.charAt(i + 1) == '+') {
builder.deleteCharAt(i);
}
}
for (int i = 0; i < builder.length(); i++) {
if (i < builder.length() - 1 && builder.charAt(i) == '*' && builder.charAt(i + 1) == '>') {
builder.deleteCharAt(i);
}
}
for (int i = 0; i < builder.length(); i++) {
if (i < builder.length() - 1 && builder.charAt(i) == '>' && builder.charAt(i + 1) == '*') {
builder.deleteCharAt(i + 1);
}
}
for (int i = 0; i < builder.length(); i++) {
if (i < builder.length() - 1 && builder.charAt(i) == '*' && builder.charAt(i + 1) == ':') {
builder.deleteCharAt(i + 1);
}
}
return builder.toString();
}
答案 0 :(得分:4)
这个问题是要求一个简单类型的方程的简单解析器。我假设你不需要用括号和奇怪的符号支持各种不规则方程。
为了安全起见,我会使用大量String.split()
代替正则表达式。
(相对)简单的解决方案将执行以下操作:
->
+
每个级别的解析都可以在一个单独的方法中轻松完成。使用正则表达式可能是解析单个分子的最佳方法,所以我从这里借用了表达式:https://codereview.stackexchange.com/questions/2345/simplify-splitting-a-string-into-alpha-and-numeric-parts。正则表达式非常简单,所以请耐心等待:
import java.util.Map;
import java.util.HashMap;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class SimpleChemicalEquationParser
{
// Counts of elements on each side
private Map<String, Integer> left;
private Map<String, Integer> right;
public SimpleChemicalEquationParser(String eqn)
{
this.left = new HashMap<>();
this.right = new HashMap<>();
parse(eqn);
}
public boolean isBalanced()
{
return left.equals(right);
}
public boolean isSimpleBalanced()
{
return leftCount() == rightCount();
}
public int leftCount()
{
return left.values().stream().mapToInt(Integer::intValue).sum();
}
public int rightCount()
{
return right.values().stream().mapToInt(Integer::intValue).sum();
}
private void parse(String eqn)
{
String[] sides = eqn.split("->");
if(sides.length != 2) {
throw new RuntimeException("Check your equation. There should be exactly one -> symbol somewhere");
}
parseSide(sides[0], this.left);
parseSide(sides[1], this.right);
}
private void parseSide(String side, Map<String, Integer> counter)
{
String[] molecules = side.split("\\+");
for(String molecule : molecules) {
parseMolecule(molecule, counter);
}
}
private void parseMolecule(String molecule, Map<String, Integer> counter)
{
molecule = molecule.trim();
Matcher matcher = Pattern.compile("([a-zA-Z]+)\\s*([0-9]*)").matcher(molecule);
int multiplier = 1;
int endIndex = 0;
while(matcher.find()) {
String separator = molecule.substring(endIndex, matcher.start()).trim();
if(!separator.isEmpty()) {
// Check if there is a premultiplier before the first element
if(endIndex == 0) {
String multiplierString = molecule.substring(0, matcher.start()).trim();
try {
multiplier = Integer.parseInt(multiplierString);
} catch(NumberFormatException nfe) {
throw new RuntimeException("Invalid prefix \"" + multiplierString +
"\" to molecule \"" + molecule.substring(matcher.start()) + "\"");
}
} else {
throw new RuntimeException("Nonsensical characters \"" + separator +
"\" in molecule \"" + molecule + "\"");
}
}
parseElement(multiplier, matcher.group(1), matcher.group(2), counter);
endIndex = matcher.end();
}
if(endIndex != molecule.length()) {
throw new RuntimeException("Invalid end to side: \"" + molecule.substring(endIndex) + "\"");
}
}
private void parseElement(int multiplier, String element, String atoms, Map<String, Integer> counter)
{
if(!atoms.isEmpty())
multiplier *= Integer.parseInt(atoms);
if(counter.containsKey(element))
multiplier += counter.get(element);
counter.put(element, multiplier);
}
public static void main(String[] args)
{
// Collect all command line arguments into one equation
StringBuilder sb = new StringBuilder();
for(String arg : args)
sb.append(arg).append(' ');
String eqn = sb.toString();
SimpleChemicalEquationParser parser = new SimpleChemicalEquationParser(eqn);
boolean simpleBalanced = parser.isSimpleBalanced();
boolean balanced = parser.isBalanced();
System.out.println("Left: " + parser.leftCount());
for(Map.Entry<String, Integer> entry : parser.left.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
System.out.println();
System.out.println("Right: " + parser.rightCount());
for(Map.Entry<String, Integer> entry : parser.right.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
System.out.println();
System.out.println("Atom counts match: " + simpleBalanced);
System.out.println("Elements match: " + balanced);
}
}
所有工作都由parse
方法完成,它是下属,它构成了一种虚拟调用树。由于这种方法特别容易确保每个元素的原子实际上是平衡的,所以我已经完成了这一步。此类打印等式两边的原子计数,无论原始计数是否平衡,以及它们是否与我的元素类型匹配。以下是几个示例运行:
$ java -cp . SimpleChemicalEquationParser '12 C O2 + 6 H2O -> 2 C6H12O6 + 12 O2'
Left: 54
C: 12
H: 12
O: 30
Right: 72
C: 12
H: 24
O: 36
Atom counts match: false
Elements match: false
$ java -cp . SimpleChemicalEquationParser '12 C O2 + 6 H2O + 6 O3 -> 2 C6H12O6 + 12 O2'
Left: 72
C: 12
H: 12
O: 48
Right: 72
C: 12
H: 24
O: 36
Atom counts match: true
Elements match: false
$ java -cp . SimpleChemicalEquationParser '12 C O2 + 12 H2O -> 2 C6H12O6 + 12 O2'
Left: 72
C: 12
H: 24
O: 36
Right: 72
C: 12
H: 24
O: 36
Atom counts match: true
Elements match: true
请注意,我在C
中的O
和CO2
之间添加了空格。这是因为我目前的分子正则表达式([a-zA-Z]+)\\s*([0-9]*)
允许任何字母组合来表示一个元素。如果您的元素始终是简单的单字母元素,请将其更改为([a-zA-Z])\\s*([0-9]*)
(删除+
量词)。如果它们将被正确命名,则第二个字母的两个字母组合总是小写,请改为:([A-Z][a-z]?)\\s*([0-9]*)
。我推荐后一种选择。对于这两个修改版本,将不再需要C O2
中的空格。
答案 1 :(得分:2)
因此,每当我需要使用Java
解析某些文字时,我最终只会使用Regex
。所以我建议你也这样做。
您可以在regex101.com处测试正则表达式。
还可以在Java
:
final inputText = ...
final Pattern pattern = Patern.compile("Some regex code");
final Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
System.out.println(matcher.group(0));
}
在Regex
内,您可以使用(
和)
定义捕获组,然后按matcher.group(int)
抓取结果。
例如,您可以先使用(.*) -> (.*)
分隔等式。
然后使用find
使用(\d+) (\w+)(?: \+| -|$)
循环左右组。
之后,您可以使用group(1)
作为金额,使用group(2)
作为元素。
如果需要,还使用(\w)(\d?)
迭代第二组(元素)以进行精确的元素分布。然后第一个组是元素,例如对于文本CO2
,它产生两个命中,第一个命中有group(1) -> C
而没有第二个组。第二次点击有group(1) -> O
和group(2) -> 2
。
在此处测试您的正则表达式:regex101#Q6KMJo
答案 2 :(得分:0)
正确的解析器(如ANTLR)的工作方式是1)将文本转换为词法标记流,然后2)将前瞻的标记解析为解析树。
Lookahead有助于了解何时“结束”解析的特定结构级别。
根据您的要求,您可以跳过lexing和parsing之间的区别,只需直接从文本中解析 - 但是,对先行的理解和使用可能会有用。
特别是一个缓冲区用于保存即将到来的(剩余的)文本,测试匹配(例如正则表达式),并从前面消耗匹配可能是有用的。这可以通过修改remaining
字符串或通过推进其中的索引来实现。
给定这样的缓冲区,您的伪代码可能如下所示:
class EquationParser {
protected ParseBuffer buffer;
// parse Equation;
// -- of form "Sum -> Sum".
// -- eg. "12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2"
public Equation parseEquation() {
Sum left = parseSum();
matchAndConsume("->");
Sum right = parseSum();
return new Equation( left, right);
}
// parse Sum;
// -- eg. "12 CO2 + 6 H2O"
public Sum parseSum() {
// parse 1 or more Product terms;
Sum result = new Sum();
result.add( parseProduct());
while (buffer.lookaheadMatch("\\+")) {
buffer.consumeMatch("\\+");
result.add( parseProduct());
}
return result;
}
// parse Product term;
// -- of form "N formula", or just "formula".
// -- eg. "12 CO2" or "CO2"
public Product parseProduct() {
int quantity = 1;
if (buffer.lookaheadMatch("\\d+")) {
quantity = Integer.parseInt( buffer.consumeMatch("\\d+"));
}
Formula formula = parseFormula();
return new Product( quantity, formula);
}
// parse Formula;
// -- eg. "C6H12O6" or "CO2"
public Formula parseFormula() {
Formula result = new Formula();
result.add( parseTerm());
while (buffer.lookaheadMatch("[A-Z][a-z]?\\d*")) {
result.add( parseTerm());
}
return result;
}
// parse Term;
// -- eg. "C6", "C", "Co6", or "Co6"
public Term parseTerm() {
// ... reader exercise to implement...
}
protected void matchAndConsume (String patt) {
if (! buffer.lookaheadMatch( patt))
throw ParseFailed("parse failed: expected "+patt);
buffer.consumeMatch( patt);
}
}
这是概念性示例代码,未经过测试&amp;不包括缓冲区或完整的解析器 - 阅读器的工作就是将这些解析为完整的解决方案。