【问题标题】:Regex for parsing a chemical equation in Java用于在 Java 中解析化学方程式的正则表达式
【发布时间】:2017-03-06 22:57:07
【问题描述】:

我有一个程序应该从控制台读取一些字符串。如果出现字符串“end”,则应该开始计算并在控制台中写入字符串。

我正在阅读的字符串是一个化学方程式。等式被这两个字符分开:->。我应该证明两边的原子数量是否相同。我找到了this 帖子并尝试实现它,但我的正则表达式有问题。

例如:

如果公式前有数字,我的正则表达式可以读取和计算化学方程式:

2 HCl + 2 Na -> 2 NaCl + H2

但如果没有数字,则计算不正确:

HCl + Na -> NaCl + H2

我的代码:

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);
    }
    int before = 0;
    int after = 0;
    list.remove(list.size() - 1);

    for (int i = 0; i < list.size(); i++) {
        String string = list.get(i);
        string = string.replace("-", "");
        String[] splitted = string.split(">");
        Pattern firstPattern = Pattern.compile("(\\d+) (\\w+)");
        Matcher firstMatcher = firstPattern.matcher(splitted[0]);
        while (firstMatcher.find()) {
            int element = Integer.parseInt(firstMatcher.group(1));
            String count = firstMatcher.group(2);
            final Pattern pattern = Pattern.compile("\\d+"); // the regex
            final Matcher matcher = pattern.matcher(count); // your string
            final ArrayList<Integer> ints = new ArrayList<Integer>(); // results
            while (matcher.find()) { // for each match
                ints.add(Integer.parseInt(matcher.group())); // convert to
                                                                // int
            }
            for (int j = 0; j < ints.size(); j++) {
                before = before + element * ints.get(j);
            }

        }
        Pattern secondPattern = Pattern.compile("(\\d+) (\\w+)");
        Matcher secondMatcher = secondPattern.matcher(splitted[1]);
        while (secondMatcher.find()) {
            int element = Integer.parseInt(secondMatcher.group(1));
            String count = secondMatcher.group(2);
            final Pattern pattern = Pattern.compile("\\d+"); // the regex
            final Matcher matcher = pattern.matcher(count); // your string
            final ArrayList<Integer> ints = new ArrayList<Integer>(); // results
            while (matcher.find()) { // for each match
                ints.add(Integer.parseInt(matcher.group())); // convert to
                                                                // int
            }
            for (int j = 0; j < ints.size(); j++) {
                after = after + element * ints.get(j);
            }
        }
        if (before == after) {
            System.out.println("formally correct");
        } else {
            System.out.println("incorrect");
        }
    }
}

以下是一些可供尝试的示例化学方程式:

输入:

HCl + Na -> NaCl + H2

2 HCl + 2 Na -> 2 NaCl + H2

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

结束

输出:

不正确

正式正确

不正确

【问题讨论】:

  • 您使用\\d+,这意味着该数字重复了一次或多次。您能否尝试改用\\d* 看看会发生什么?
  • @Anton 它不工作它写了一个数字格式异常
  • 我正在努力确保我理解问题所在。所以“2 HCl + 2 Na -> 2 NaCl + H2”是输入,结果正确吗?是“->”之后的正确信息项目原谅我,我忘记了高中化学。元素既可以是 2 个字母(NA)也可以是 1 个字母(O)?起初,我认为可能将像这样的(CO2)与空格(CO2)分开并将其包含在正则表达式中。 + 可以很好地拆分它,并且应该与差异混淆。哦,有没有 count 超过 2 位的情况?这也可能有帮助 [链接] (regexplanet.com/advanced/java/index.html)
  • 我不确定到底需要什么。只是匹配前后和不匹配的数字?在“->”之前的部分?
  • @SparkleGoat 抱歉,我没有解释得这么好。 1.“2 HCl + 2 Na -> 2 NaCl + H2”是输入字符串,它是正确的。元素可以是 2 个字母或 1 个字母。如果有一个元素有 2 个字母,那么第二个字母写得很小。计数可能会上升到无限(理论上这是不可能的,但你永远不知道输入是什么)

标签: java regex


【解决方案1】:

所以,我可以在您的逻辑中找到以下问题:

  1. 您正在使用Pattern.compile("(\\d+) (\\w+)") 来匹配两边的每个组件。在您尝试匹配1 or more digits followed by a space followed by 1 or more word characters 的模式中。但是在那里,数字是可选的。所以你需要它是\\d* 而不是第一个捕获组的内容。此外,空间是可选的。所以你需要在模式中指定相同的内容。为了避免数字与第二个捕获组匹配(因为第一个组变为可选),您需要使用([A-Z]\\w*)。这确保了数字(如果有的话)将与第一组本身匹配。所以你匹配两边每个组件的模式应该是Pattern.compile("(\\d*) ?([A-Z]\\w*)")
  2. 您正在使用 Pattern.compile("\\d+") 来匹配原子计数(如 H2 中的 2)。这样,如果任何元素具有单个原子,即如果您有CaCl2,您将错过计算单个原子,您必须将其计为 1 个 Ca 原子和 2 个 Cl 原子。为此,您需要分别匹配每个元素,这可以使用 Pattern.compile("[A-Z][a-z]*(\\d*)") 之类的模式来完成。

  3. 您没有以正确的方式计算总数。将每个元素的分子和原子计数默认为 1,然后将它们与每个元素相乘,然后将所有乘积相加得到总计数。

还有 2 条建议:

  1. 由于两边的计算逻辑相同,因此定义一个函数并调用两次。
  2. -&gt; 自己拆分。我认为您不需要先删除连字符,然后再按&gt; 拆分。

在进入代码之前尝试自己修改逻辑

这是我定义计算一侧总和的函数的方式:

private static int calculateCount(String eqPart) {
    Matcher matcher = Pattern.compile("(\\d*) ([A-Z]\\w*)").matcher(eqPart);
    int totalCount = 0;
    while (matcher.find()) {
        String moleculeCountStr = matcher.group(1);
        int moleculeCount = moleculeCountStr.isEmpty() ? 1 : Integer.parseInt(moleculeCountStr);
        String molecule = matcher.group(2);
        Matcher moleculeMatcher = Pattern.compile("[A-Z][a-z]*(\\d*)").matcher(molecule);
        while (moleculeMatcher.find()) {
            String atomCountStr = moleculeMatcher.group(1);
            int atomCount = atomCountStr.isEmpty() ? 1 : Integer.parseInt(atomCountStr);
            totalCount += moleculeCount * atomCount;
        }
    }
    return totalCount;
}

使用每个拆分结果调用函数(通过-&gt;)并比较总数以查看等式是否正确。

【讨论】:

  • 谢谢你,这正是我需要的!
【解决方案2】:

我不确定这是否是您需要的。但要获得等式的各个部分,可以使用以下regex

\w+ // matches any word character (equal to [a-zA-Z0-9_])

详情请点击链接。要获取字符串的左右部分,我们可以使用"HCl + Na -&gt; NaCl + H2".split("-&gt;") 将其拆分。之后我们可以进行计算:

final Pattern pattern = Pattern.compile("\\w+");
Arrays.asList(
            "HCl + Na -> NaCl + H2",

            "2 HCl + 2 Na -> 2 NaCl + H2",

            "12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2"
).stream().flatMap(s -> Stream.of(s.split("->"))
).peek(s -> System.out.println("part of equation: " + s))
.forEach(s -> 
        {
          Matcher match = pattern.matcher(s);
          while (match.find()) {
            System.out.println(match.group());
          }
        }
);

我希望这会有所帮助。

【讨论】:

  • 你不会贪婪地拉整个字符串吗?像 (.*)\w+ 带回整个 HCl + Na ,而 \w+ 带回单个字母。
  • @SparkleGoat,如果我没有遗漏任何东西,\w? 将带来单独的信件。 ? 在这种情况下将意味着零或一次补充。 \w+ 一次或无限次。
  • 好吧,他正试图将“2 HCl + 2 Na”分解为 [2,HCI, 2, Na] ?为了数数。我想我明白了,在这种情况下 \w+ 是一个有效的解决方案:D
  • @Anton 感谢您的回答,但“Arrays.list .....”存在错误。你可能知道我做错了什么吗?
  • @Anton 看下面我的评论
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多