Разделить строку различной длины с помощью регулярных выражений

Я не знаю, возможно ли это с помощью регулярных выражений. Я просто спрашиваю, если кто-то знает ответ.

у меня есть string ="hellohowareyou??", Мне нужно разделить это так

[h, el, loh, owar, eyou?, ?],

Разделение выполняется так, что первая строка будет иметь длину 1, вторую длину 2 и т. Д. Последняя строка будет содержать оставшиеся символы. Я могу сделать это легко без регулярных выражений, используя такую ​​функцию.

public ArrayList<String> splitString(String s)
    {
        int cnt=0,i;
        ArrayList<String> sList=new ArrayList<String>();
        for(i=0;i+cnt<s.length();i=i+cnt)
        {
         cnt++;
         sList.add(s.substring(i,i+cnt));    
        }
        sList.add(s.substring(i,s.length()));
        return sList;
    }

Мне было просто любопытно, можно ли сделать это с помощью регулярных выражений.

3 ответа

Решение

Решение

Следующий фрагмент кода генерирует шаблон, который выполняет эту работу ( см. Его запуск на ideone.com):

// splits at indices that are triangular numbers
class TriangularSplitter {

  // asserts that the prefix of the string matches pattern
  static String assertPrefix(String pattern) {
    return "(?<=(?=^pattern).*)".replace("pattern", pattern);
  }
  // asserts that the entirety of the string matches pattern
  static String assertEntirety(String pattern) {
    return "(?<=(?=^pattern$).*)".replace("pattern", pattern);
  }
  // repeats an assertion as many times as there are dots behind current position
  static String forEachDotBehind(String assertion) {
    return "(?<=^(?:.assertion)*?)".replace("assertion", assertion);
  }

  public static void main(String[] args) {
    final String TRIANGULAR_SPLITTER =
      "(?x) (?<=^.) | measure (?=(.*)) check"
        .replace("measure", assertPrefix("(?: notGyet . +NBefore +1After)*"))
        .replace("notGyet", assertPrefix("(?! \\1 \\G)"))
        .replace("+NBefore", forEachDotBehind(assertPrefix("(\\1? .)")))
        .replace("+1After", assertPrefix(".* \\G (\\2?+ .)"))
        .replace("check", assertEntirety("\\1 \\G \\2 . \\3"))
        ;
    String text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    System.out.println(
        java.util.Arrays.toString(text.split(TRIANGULAR_SPLITTER))
    );
    // [a, bc, def, ghij, klmno, pqrstu, vwxyzAB, CDEFGHIJ, KLMNOPQRS, TUVWXYZ]
  }
}

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

Рекомендации

Это краткое описание основных конструкций регулярных выражений:

  • (?x) является модификатором встроенного флага для включения режима свободного пробела, где неэкранированные пробелы игнорируются (и # можно использовать для комментариев).
  • ^ а также $ являются якорями начала и конца строки. \G является привязкой конца предыдущего совпадения.
  • | обозначает чередование (то есть "или").
  • ? как спецификатор повторения обозначает необязательный (то есть ноль или один из). В качестве квантификатора повторения, например, в .*? это означает, что * (то есть ноль или более) повторение неохотно/ не жадный.
  • (…) используются для группировки. (?:…) это группа без захвата. Группа захвата сохраняет строку, которой она соответствует; он позволяет, помимо прочего, сопоставлять ссылки назад / вперед / вложенные (например, \1).
  • (?=…) позитивный взгляд; он смотрит вправо, чтобы утверждать, что есть совпадение данного образца.(?<=…) это позитивный взгляд сзади; это смотрит налево.
  • (?!…) является негативным взглядом; он смотрит вправо, чтобы утверждать, что нет совпадения шаблона.

Смежные вопросы


объяснение

Шаблон совпадает с утверждениями нулевой ширины. Довольно сложный алгоритм используется, чтобы утверждать, что текущая позиция является треугольным числом. Есть 2 основных варианта:

  • (?<=^.)то есть мы можем посмотреть назад и увидеть начало строки на расстоянии одной точки
    • Это соответствует индексу 1 и является важной отправной точкой для остальной части процесса
  • В противном случае мы measure восстановить, как был сделан последний матч (используя \G в качестве контрольной точки), сохраняя результат измерения в "до" \G и после" \G захват групп. Мы тогда check если текущая позиция - та, которая предписана измерением, чтобы найти, где должно быть сделано следующее совпадение.

Таким образом, первая альтернатива является тривиальным "базовым случаем", а вторая альтернатива устанавливает, как сделать все последующие совпадения после этого. У Java нет пользовательских групп, но вот семантика для 3 групп захвата:

  • \1 захватывает строку "до" \G
  • \2 захватывает некоторую строку "после" \G
  • Если длина \1 например, 1 + 2 + 3 +... + k, то длина \2 должен быть к.
    • следовательно \2 . имеет длину к +1 и должен быть следующей частью в нашем split!
  • \3 захватывает строку справа от нашей текущей позиции
    • Следовательно, когда мы можем assertEntirety на \1 \G \2 . \3мы подходим и устанавливаем новый \G

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

Чтобы проиллюстрировать, как это работает, давайте рассмотрим пример. Давайте принимать abcdefghijklm в качестве входных данных, и сказать, что мы уже частично отделены [a, bc, def],

          \G     we now need to match here!
           ↓       ↓
a b c d e f g h i j k l m n
\____1____/ \_2_/ . \__3__/   <--- \1 G \2 . \3
  L=1+2+3    L=3           

Помни что \G отмечает конец последнего совпадения, и это происходит по индексам треугольных чисел. Если \G произошло в 1 + 2 + 3 +... + k, тогда следующий матч должен быть k+1 позиции после \G быть треугольным индексом числа.

Таким образом, в нашем примере, где \G где мы только что откололись def, мы измерили, что k = 3, и следующий матч будет отделен ghij как и ожидалось.

Иметь \1 а также \2 быть построены в соответствии с вышеуказанной спецификацией, мы в основном делаем while "петля": до тех пор, пока это notGyetмы считаем до k следующим образом:

  • +NBeforeт.е. расширяем \1 одним forEachDotBehind
  • +1Afterт.е. расширяем \2 только одним

Обратите внимание, что notGyet содержит прямую ссылку на группу 1, которая будет определена позже в шаблоне. По сути мы делаем цикл до \1 "хиты" \G,


Заключение

Излишне говорить, что это конкретное решение имеет ужасную производительность. Движок регулярных выражений помнит только ГДЕ было сделано последнее совпадение (с \G) и забывает, КАК (то есть все группы захвата сбрасываются при следующей попытке сопоставления). Наш шаблон должен затем восстановить HOW (ненужный шаг в традиционных решениях, где переменные не так "забывчивы"), кропотливо создавая строки, добавляя по одному символу за раз (что является O(N^2)). Каждое простое измерение является линейным, а не постоянным временем (так как оно выполняется в виде совпадения строк, где длина является фактором), и, в дополнение к этому, мы делаем много измерений, которые являются избыточными (то есть, чтобы увеличить на единицу, нам нужно сначала повторно сопоставить что у нас уже есть).

Вероятно, есть много "лучших" решений регулярных выражений, чем это. Тем не менее, сложность и неэффективность этого конкретного решения должны справедливо предполагать, что регулярное выражение не предназначено для такого типа сопоставления с образцом.

Тем не менее, в учебных целях, это абсолютно замечательная проблема, потому что есть множество знаний в области исследования и формулирования ее решений. Надеемся, что это конкретное решение и его объяснение было поучительным.

Цель Regex - распознавать шаблоны. Здесь вы ищете не шаблоны, а разделение по длине. Так что регулярные выражения не подходят.

Это возможно, но не с одним регулярным выражением: чтобы найти первые n символов с помощью регулярного выражения, вы используете: "^(.{N}).*"

Таким образом, вы можете искать с помощью этого регулярного выражения для 1-го символа. Затем вы создаете подстроку и ищете следующие 2 символа. И т.п.

Как сказал @splash, это сделает код более сложным и неэффективным, так как вы используете регулярные выражения для чего-то вне их предназначения.

String a = "hellohowareyou??";
int i = 1;

    while(true) {

        if(i >= a.length()) {
            System.out.println(a);
            break;
        }

        else {
            String b = a.substring(i++);
            String[] out = a.split(Pattern.quote(b) + "$");
            System.out.println(out[0]);
            a = b;
            if(b.isEmpty())
                break;
        }

    }
Другие вопросы по тегам