13

我正在制作一个基于文本的掷骰子。它接受像“2d10+5”这样的字符串,并返回一个字符串作为滚动的结果。我的问题出现在标记器中,该标记器将字符串拆分为有用的部分,以便我解析为信息。

String[] tokens = message.split("(?=[dk\\+\\-])");

这产生了奇怪的、意想不到的结果。我不知道究竟是什么导致了他们。可能是正则表达式,我的误解,或者 Java 只是 Java。这是正在发生的事情:

  • 3d6+4产生字符串数组[3, d6, +4]。这是对的。
  • d%产生字符串数组[d%]。这是对的。
  • d20产生字符串数组[d20]。这是对的。
  • d%+3产生字符串数组[, d%, +3]。这是不正确的。
  • d20+2产生字符串数组[, d20, +2]。这是不正确的。

在第四个和第五个示例中,奇怪的是导致一个额外的空字符串出现在数组的前面。这不是字符串前面缺少数字,因为其他示例反驳了这一点。这不是百分号的存在,也不是加号。

现在我只是在空白字符串上继续执行 for 循环,但这感觉有点像创可贴解决方案。有谁知道是什么导致了数组前面的空白字符串?我该如何解决?

4

3 回答 3

13

挖掘源代码,我得到了这种行为背后的确切问题。

String.split()方法内部使用Pattern.split(). 在返回结果数组之前的 split 方法检查最后一个匹配的索引或者是否真的有匹配。如果最后匹配的索引是0,则意味着您的模式仅匹配字符串开头的空字符串或根本不匹配,在这种情况下,返回的数组是包含相同元素的单元素数组。

这是源代码:

public String[] split(CharSequence input, int limit) {
        int index = 0;
        boolean matchLimited = limit > 0;
        ArrayList<String> matchList = new ArrayList<String>();
        Matcher m = matcher(input);

        // Add segments before each match found
        while(m.find()) {
            if (!matchLimited || matchList.size() < limit - 1) {
                String match = input.subSequence(index, m.start()).toString();
                matchList.add(match);

                // Consider this assignment. For a single empty string match
                // m.end() will be 0, and hence index will also be 0
                index = m.end();
            } else if (matchList.size() == limit - 1) { // last one
                String match = input.subSequence(index,
                                                 input.length()).toString();
                matchList.add(match);
                index = m.end();
            }
        }

        // If no match was found, return this
        if (index == 0)
            return new String[] {input.toString()};

        // Rest of them is not required

如果上述代码中的最后一个条件 - index == 0, 为真,则返回单元素数组和输入字符串。

现在,考虑indexcan的情况0

  1. 当根本没有匹配时。(正如已经在该条件上方的评论中)
  2. 如果在开头找到匹配,并且匹配字符串的长度为0,则if块中(while循环内)的索引值 -

    index = m.end();
    

    将为 0。唯一可能的匹配字符串是空字符串(长度 = 0)。这正是这里的情况。而且不应该有任何进一步的匹配,否则index将更新为不同的索引。

因此,考虑到您的情况:

  • 对于d%,模式只有一个匹配,在第一个 之前d。因此,索引值为0。但由于没有进一步匹配,索引值没有更新,if条件变为true,并返回原始字符串的单元素数组。

  • 因为d20+2会有两场比赛,一场 before d,一场 before +。因此索引值将被更新,因此ArrayList将返回上述代码中的,其中包含作为分隔符拆分结果的空字符串,分隔符是字符串的第一个字符,正如@Stema 的回答中已经解释的那样。

因此,要获得您想要的行为(仅当分隔符不在开头时才在分隔符上拆分,您可以在正则表达式模式中添加否定的后视):

"(?<!^)(?=[dk+-])"  // You don't need to escape + and hyphen(when at the end)

这将拆分为空字符串,后跟您的字符类,但前面没有字符串的开头。


考虑"ad%"在正则表达式模式 - 上拆分字符串的情况"a(?=[dk+-])"。这将为您提供一个数组,其中第一个元素为空字符串。这里唯一的变化是,空字符串被替换为a

"ad%".split("a(?=[dk+-])");  // Prints - `[, d%]`

为什么?那是因为匹配字符串的长度是1. 所以第一次匹配后的索引值 -m.end()不会是0but 1,因此不会返回单个元素数组。

于 2013-09-18T11:43:37.597 回答
5

我很惊讶它不会发生在案例 2 和案例 3 中,所以这里真正的问题是

为什么“d20”和“d%”开头没有空字符串

正如 Rohit Jain 在他的详细分析中解释的那样,当在字符串的开头只找到一个匹配并且 match.end 索引为 0 时,就会发生这种情况。(这只会发生在仅使用环视断言来查找匹配)。

问题是,d%+3从您要拆分的字符开始。因此,您的正则表达式在第一个字符之前匹配,并且您在开始时会得到一个空字符串。

您可以添加一个lookbehind,以确保您的表达式在字符串的开头不匹配,因此它不会在那里拆分:

String[] tokens = message.split("(?<!^)(?=[dk\\+\\-])");

(?<!^)是一个后向断言,当它不在字符串的开头时,它是真的。

于 2013-09-18T11:22:23.560 回答
0

我建议简单匹配而不是拆分:

Matcher matcher = Pattern.compile("([1-9]*)(d[0-9%]+)([+-][0-9]+)?").matcher(string);
if(matcher.matches()) {
    String first = matcher.group(1);
    // etc
}

不能保证正则表达式,但我认为它会做......

于 2013-09-18T11:33:11.413 回答