Несколько совпадений с разделителем

это мое регулярное выражение:

([+-]*)(\\d+)\\s*([a-zA-Z]+)
  • группа №1 = знак
  • группа no.2 = множитель
  • группа № 3 = единица времени

Дело в том, что я хотел бы сопоставить данный ввод, но он может быть "цепным". Поэтому мой ввод должен быть действительным тогда и только тогда, когда весь шаблон повторяется без каких-либо событий между этими вхождениями (за исключением пробелов). (Только одно совпадение или несколько совпадений рядом друг с другом с возможными пробелами между ними). ​​

действительные примеры:

1day
+1day
-1 day
+1day-1month
+1day +1month
   +1day  +1month    

Недопустимые примеры:

###+1day+1month
+1day###+1month
+1day+1month###
###+1day+1month###
###+1day+1month###

В моем случае я могу использовать метод matcher.find(), это сделало бы трюк, но он примет вход вроде этого: +1day###+1month, который недействителен для меня.

Любые идеи? Это можно решить с помощью нескольких условий IF и нескольких проверок для начальных и конечных индексов, но я ищу элегантное решение.

ИЗМЕНИТЬ

Предлагаемое регулярное выражение в комментариях ниже ^\s*(([+-]*)(\d+)\s*([a-zA-Z]+)\s*)+$ будет частично выполнять трюк, но если я его использую в коде ниже, он возвращает другой результат, чем результат, который я ищу. Проблема в том, что я не могу использовать (*my regex*)+, потому что он будет соответствовать всему.

Решение может состоять в том, чтобы сопоставить весь ввод с ^\s*(([+-]*)(\d+)\s*([a-zA-Z]+)\s*)+$, а затем использовать ([+-]*)(\\d+)\\s*([a-zA-Z]+) с matcher.find() и matcher.group(i), чтобы извлечь каждое соответствие и его группы. Но я искал более элегантное решение.

Ответ 1

Это должно сработать для вас:

^\s*(([+-]*)(\d+)\s*([a-zA-Z]+)\s*)+$

Во-первых, добавив начальные и конечные привязки (^ и $), шаблон не допустит появления недопустимых символов где-либо до или после матча.

Далее я включил опциональные пробелы до и после повторного шаблона (\s*).

Наконец, весь шаблон заключен в ретранслятор, так что он может встречаться несколько раз подряд ((...)+).

На стороне, заметьте, я также рекомендовал бы изменить [+-]* на [+-]?, чтобы он мог произойти только один раз.

Онлайн-демонстрация

Ответ 2

Вы можете использовать ^$ для этого, чтобы соответствовать началу/концу строки

^\s*(?:([+-]?)(\d+)\s*([a-z]+)\s*)+$

https://regex101.com/r/lM7dZ9/2

См. Unit Tests для ваших примеров. В принципе, вам просто нужно разрешить повторение шаблона и заставить ничего, кроме пробелов, между матчами.

В сочетании с совпадением начала и конца строки, и все готово.

Ответ 3

Вы можете использовать String.matches или Matcher.matches в Java для соответствия всей области.

Пример Java:

public class RegTest {

    public static final Pattern PATTERN = Pattern.compile(
            "(\\s*([+-]?)(\\d+)\\s*([a-zA-Z]+)\\s*)+");

    @Test
    public void testDays() throws Exception {
        assertTrue(valid("1 day"));
        assertTrue(valid("-1 day"));
        assertTrue(valid("+1day-1month"));
        assertTrue(valid("+1day -1month"));
        assertTrue(valid("   +1day  +1month   "));

        assertFalse(valid("+1day###+1month"));
        assertFalse(valid(""));
        assertFalse(valid("++1day-1month"));
    }

    private static boolean valid(String s) {
        return PATTERN.matcher(s).matches();
    }
}

Ответ 4

Вы можете выполнить следующее:

String p = "\\G\\s*(?:([-+]?)(\\d+)\\s*([a-z]+)|\\z)";

Pattern RegexCompile = Pattern.compile(p, Pattern.CASE_INSENSITIVE);

String s = "+1day 1month";

ArrayList<HashMap<String, String>> results = new ArrayList<HashMap<String, String>>(); 

Matcher m = RegexCompile.matcher(s);
boolean validFormat = false;        

while( m.find() ) {
    if (m.group(1) == null) {
        // if the capture group 1 (or 2 or 3) is null, it means that the second
        // branch of the pattern has succeeded (the \z branch) and that the end
        // of the string has been reached. 
        validFormat = true;
    } else {
        // otherwise, this is not the end of the string and the match result is
        // "temporary" stored in the ArrayList 'results'
        HashMap<String, String> result = new HashMap<String, String>();
        result.put("sign", m.group(1));
        result.put("multiplier", m.group(2));
        result.put("time_unit", m.group(3));
        results.add(result);
    }
}

if (validFormat) {
    for (HashMap item : results) {
        System.out.println("sign: " + item.get("sign")
                         + "\nmultiplier: " + item.get("multiplier")
                         + "\ntime_unit: " + item.get("time_unit") + "\n");
    }
} else {
    results.clear();
    System.out.println("Invalid Format");
}

Якорь \G соответствует началу строки или позиции после предыдущего совпадения. В этом шаблоне он гарантирует, что все совпадения совпадают. Если конец строки достигнут, это доказательство того, что строка действительна от начала до конца.