Regex разделяется на перекрывающиеся строки - PullRequest
3 голосов
/ 13 марта 2010

Я изучаю возможности регулярных выражений, поэтому мне просто интересно, возможно ли что-то подобное:

public class StringSplit {
    public static void main(String args[]) {
        System.out.println(
            java.util.Arrays.deepToString(
                "12345".split(INSERT_REGEX_HERE)
            )
        ); // prints "[12, 23, 34, 45]"
    }
}

Если возможно, просто предоставьте регулярное выражение (и, предпочтительно, некоторые пояснения о том, как оно работает).

Если это возможно только в некоторых разновидностях регулярных выражений, отличных от Java, не стесняйтесь предоставлять их также.

Если это невозможно, объясните, пожалуйста, почему.


БОНУСНЫЙ ВОПРОС

Тот же вопрос, но с циклом find() вместо split:

    Matcher m = Pattern.compile(BONUS_REGEX).matcher("12345");
    while (m.find()) {
        System.out.println(m.group());
    } // prints "12", "23", "34", "45"

Обратите внимание, что у меня не так много конкретных задач для выполнения тем или иным способом, скорее я хочу понимать регулярные выражения. Мне не нужен код, который делает то, что я хочу; Мне нужны регулярные выражения, если они существуют, которые я могу использовать в приведенном выше коде для выполнения задачи (или регулярные выражения в других разновидностях, которые работают с «прямым переводом» кода на другой язык).

А если их не существует, я бы хотел получить хорошее веское объяснение, почему.

Ответы [ 5 ]

5 голосов
/ 13 марта 2010

Я не думаю, что это возможно с split(), но с find() это довольно просто. Просто используйте взгляд с группой захвата внутри:

Matcher m = Pattern.compile("(?=(\\d\\d)).").matcher("12345");
while (m.find())
{
  System.out.println(m.group(1));
}

Многие люди не понимают, что к тексту, захваченному во взгляде или взгляде сзади, можно ссылаться после матча, как и в любом другом захвате. Это особенно нелогично в этом случае, когда захват является надмножеством «целого» совпадения.

На самом деле, это работает, даже если регулярное выражение в целом ничего не соответствует. Удалите точку из регулярного выражения выше ("(?=(\\d\\d))"), и вы получите тот же результат. Это связано с тем, что всякий раз, когда при успешном сопоставлении не используется никаких символов, механизм регулярных выражений автоматически поднимается на одну позицию перед попыткой повторного сопоставления, чтобы предотвратить бесконечные циклы.

Нет split() эквивалента для этой техники, хотя, по крайней мере, не в Java. Несмотря на то, что вы можете разделить обходные пути и другие утверждения нулевой ширины, нет способа заставить один и тот же символ появляться в более чем одной из результирующих подстрок.

4 голосов
/ 13 марта 2010

Эта несколько тяжелая реализация, использующая Matcher.find вместо split, также будет работать, хотя к тому времени, когда вам придётся закодировать цикл for для такой тривиальной задачи, вы также можете вообще отбросить регулярные выражения и использовать подстроки (для аналогичной сложности кодирования минус циклы процессора):

import java.util.*;
import java.util.regex.*;

public class StringSplit { 
    public static void main(String args[]) { 
        ArrayList<String> result = new ArrayList<String>();
        for (Matcher m = Pattern.compile("..").matcher("12345"); m.find(result.isEmpty() ? 0 : m.start() + 1); result.add(m.group()));
        System.out.println( result.toString() ); // prints "[12, 23, 34, 45]" 
    } 
} 

EDIT1

match(): причина, по которой никто до сих пор не смог придумать регулярное выражение, подобное вашему BONUS_REGEX, лежит в Matcher, что возобновит поиск следующей группы, в которой закончилась предыдущая группа (т.е. без перекрытия) , как было предложено после того, с чего начиналась предыдущая группа, то есть, если явно не указывать начальную позицию поиска (см. выше). Хорошим кандидатом на BONUS_REGEX был бы "(.\\G.|^..)", но, к сожалению, уловка \G -anchor-in-the-middle не работает с Java Match (но прекрасно работает в Perl):

 perl -e 'while ("12345"=~/(^..|.\G.)/g) { print "$1\n" }'
 12
 23
 34
 45

split(): что касается INSERT_REGEX_HERE, хорошим кандидатом был бы (?<=..)(?=..) (точка разделения - это позиция нулевой ширины, где у меня есть два символа справа и два слева), но опять же, потому что split кончается ничем не перекрывающимся, в результате вы получите [12, 3, 45] (что близко, но без сигары.)

EDIT2

Ради интереса, вы можете обмануть split(), делая то, что вы хотите, сначала удваивая неограниченные символы (здесь вам нужно зарезервированное значение символа для разделения):

Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1#$1").split("#")

Мы можем быть умными и исключить необходимость в зарезервированном символе, воспользовавшись тем, что утверждения нулевой ширины упреждающие (в отличие от упреждающих) могут иметь неограниченную длину; поэтому мы можем разделить все точки, которые на равны четному количеству символов на расстоянии от конца удвоенной строки (и по крайней мере на два символа от ее начала), давая тот же результат, что и выше:

Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1$1").split("(?<=..)(?=(..)*$)")

В качестве альтернативы обманывает match() аналогичным образом (но без необходимости в зарезервированном значении символа):

Matcher m = Pattern.compile("..").matcher(
  Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1$1")
);
while (m.find()) { 
    System.out.println(m.group()); 
} // prints "12", "23", "34", "45" 
1 голос
/ 13 марта 2010

Я не думаю, что вы можете сделать это с split (), потому что он отбрасывает часть, которая соответствует регулярному выражению.

В Perl это работает:

my $string = '12345';
my @array = ();
while ( $string =~ s/(\d(\d))/$2/ ) {
    push(@array, $1);
}
print join(" ", @array);
# prints: 12 23 34 45

Выражение поиска и замены гласит: сопоставьте первые две соседние цифры и замените их в строке только второй из двух цифр.

1 голос
/ 13 марта 2010

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

0 голосов
/ 15 марта 2010

Альтернатива, использование простого сопоставления с Perl. Должен работать в любом месте, где работают заглядывающие. И здесь нет необходимости в петлях.

 $_ = '12345';
 @list = /(?=(..))./g;
 print "@list";

 # Output:
 # 12 23 34 45

Но этот, как было опубликовано ранее, лучше, если трюк \ G работает:

 $_ = '12345';
 @list = /^..|.\G./g;
 print "@list";

 # Output:
 # 12 23 34 45

Редактировать : Извините, не увидел, что все из этого уже опубликовано.

...