解析代表化学反应的字符串并验证反应是否可行

时间:2016-12-13 19:40:03

标签: java chemistry

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

例如:

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

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

3 个答案:

答案 0 :(得分:4)

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

为了安全起见,我会使用大量String.split()代替正则表达式。

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

  1. 拆分->
  2. 确保有两件
  3. 总结每件作品:
    1. 拆分+
    2. 解析每个分子并总结原子:
      1. 解析可选乘数
      2. 查找所有匹配分子正则表达式
      3. 转换数字并按元素添加
  4. 比较结果
  5. 每个级别的解析都可以在一个单独的方法中轻松完成。使用正则表达式可能是解析单个分子的最佳方法,所以我从这里借用了表达式: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方法完成,它是下属,它构成了一种虚拟调用树。由于这种方法特别容易确保每个元素的原子实际上是平衡的,所以我已经完成了这一步。此类打印等式两边的原子计数,无论原始计数是否平衡,以及它们是否与我的元素类型匹配。以下是几个示例运行:

    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中的OCO2之间添加了空格。这是因为我目前的分子正则表达式([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) -> Ogroup(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;不包括缓冲区或完整的解析器 - 阅读器的工作就是将这些解析为完整的解决方案。