Подтвердить что ты не робот

Java String.split() иногда дает пустые строки

Я делаю текстовый ролик для кости. Он принимает строки типа "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 на пустых строках, но это похоже на решение для групповой помощи. Кто-нибудь знает, что вызывает пустую строку в передней части массива? Как я могу это исправить?

4b9b3361

Ответ 1

Копаясь в исходном коде, я получил точную проблему за этим поведением.

Метод 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, истинно, тогда массив с одним элементом возвращается со строкой ввода.

Теперь рассмотрим случаи, когда index может быть 0.

  • Если совпадения нет. (Как уже в комментарии выше этого условия)
  • Если совпадение найдено в начале, а длина строки соответствует 0, тогда значение индекса в блоке if (внутри цикла while) -

    index = m.end();
    

    будет равно 0. Единственная возможная строка соответствия - пустая строка (length = 0). Это именно так. А также не должно быть никаких дополнительных совпадений, иначе index будет обновлен до другого индекса.

Итак, учитывая ваши случаи:

  • Для d% перед шаблоном существует только одно совпадение перед первым d. Следовательно, значение индекса будет 0. Но поскольку дальнейших совпадений нет, значение индекса не обновляется, а условие if становится true и возвращает массив одиночных элементов с исходной строкой.

  • Для d20+2 должно быть два совпадения: один перед d и один до +. Таким образом, значение индекса будет обновляться, и, следовательно, будет возвращен 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() не будет 0, а 1, и, следовательно, массив элементов не будет возвращен.

Ответ 2

Я был удивлен, что этого не происходит для случаев 2 и 3, поэтому реальный вопрос здесь

Почему существует NO пустая строка в начале для "d20" и "d%"?

как объяснил Рохит Джайн в своих подробных анализах, это происходит, когда в начале строки встречается только одно совпадение, а индекс match.end равен 0. (Это может произойти только тогда, когда используется только утверждение поиска для поиска соответствия).

Проблема заключается в том, что d%+3 начинается с char, который вы разделяете. Поэтому ваше регулярное выражение соответствует перед первым символом, и вы получаете пустую строку в начале.

Вы можете добавить lookbehind, чтобы убедиться, что ваше выражение не соответствует в начале строки, чтобы оно не было разделено:

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

(?<!^) - это утверждение lookbehind, которое истинно, когда оно не находится в начале строки.

Ответ 3

Я бы рекомендовал простое сопоставление, а не разделение:

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

Нет гарантии для регулярного выражения, но я думаю, что это будет...