5

我必须编写一个程序,将用户的化学方程式作为输入,例如 12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2,并观察两个站点上的原子量是否相同。有什么方法可以轻松计算和解析吗?

例如:

12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2

12*2+6*2 -> 2*6+2*12+2*6+12*2

在这种情况下,应该有输出“假”。

这是我的代码,但实际上只是为了尝试一些东西:

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();
}
4

3 回答 3

4

这个问题要求一个简单类型的方程的简单解析器。我假设你不需要支持各种带括号和奇怪符号的不规则方程。

为了安全起见,我会使用很多String.split()而不是正则表达式。

一个(相对)简单的解决方案将执行以下操作:

  1. 拆分开->
  2. 确保有两块
  3. 总结每一块:
    1. 拆分开+
    2. 解析每个分子并总结原子:
      1. 解析可选乘数
      2. 查找分子正则表达式的所有匹配项
      3. 转换数字并按元素相加
  4. 比较结果

每个级别的解析都可以在单独的方法中轻松完成。使用正则表达式可能是解析单个分子的最佳方法,所以我从这里借用了表达式:https ://codereview.stackexchange.com/questions/2345/simplify-splitting-a-string-into-alpha-and-numeric -零件。正则表达式非常简单,所以请多多包涵:

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方法和它的下属完成的,它们构成了一种虚拟调用树。由于这种方法可以特别容易地确保每个元素的原子实际上是平衡的,所以我在这里做了。这个类打印方程两边的原子计数,原始计数是否平衡,以及它们是否与我的元素类型匹配。以下是几个示例运行:

OP的原始示例:

$ 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将不再需要空间。

于 2016-12-16T19:52:56.857 回答
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然后使用with:循环左右组(\d+) (\w+)(?: \+| -|$)

之后,您可以使用group(1)数量和group(2)元素。

如果需要,还可以使用 迭代第二组(元素)以获得精确的元素分布(\w)(\d?)。然后第一组是元素,例如对于CO2它产生两个命中的文本,第一个命中有group(1) -> C并且没有第二组。第二个命中有group(1) -> Ogroup(2) -> 2

在这里测试你的正则表达式:regex101#Q6KMJo

于 2016-12-13T20:19:10.993 回答
0

诸如 ANTLR 之类的正确解析器的工作方式是 1) 将文本转换为词汇标记流,然后 2) 将带有前瞻的标记解析为解析树。

前瞻有助于了解何时“结束”特定结构级别的解析。

根据您的要求,您可能可以跳过词法分析和解析之间的区别,而直接从文本中解析——但是,对前瞻的欣赏和使用可能会很有用。

特别是一个缓冲区来保存即将到来的(剩余的)文本,测试它的匹配(例如正则表达式),并从前面消耗匹配可能是有用的。这可以通过修改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);
    }
}

这是概念性示例代码,未经测试且不包括缓冲区或完整解析器——读者的工作是将这些内容充实为完整的解决方案。

于 2017-03-08T02:06:28.033 回答