РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ захватывает каждое слово из n букв между двумя словами предложения

#regex #regex-lookarounds

#регулярное выражение #регулярное выражение -поиск

Вопрос:

Мне трудно выбрать только слова длиной n между двумя словами предложения: например: для утверждения: «это начало, некоторые слова должны быть выбраны, конец больше не выбирается»

Допустим, я хотел бы выбрать 3 слова между словом «начало» и «конец», в результате будут записаны некоторые слова, которые выбираются без учета to и be.

https://regex101.com/r/Ost7Wn/3

Просто выбор [w] {3,} работает сам по себе, но я не могу понять, как поместить его между словами «начало» и «конец» в предложении, чтобы соответствовать моим словам из n букв, которые появляются только между ними. Я перепробовал много вещей, от поиска до захвата групп, но я действительно не могу этого понять!

Есть идеи? Спасибо

Ответ №1:

Вы можете использовать это регулярное выражение с предвидением и G :

 (?:bSTARTb|(?!^)G)h (?!ENDb).*?b(w{3,})(?=.*?bENDb)
  

Демонстрация регулярных выражений

Подробности регулярного выражения:

  • (?:bSTARTb|(?!^)G) : Сопоставьте слово START или, начиная с конца предыдущего сопоставления, сопоставьте 0 или более слов, разделенных 1 пробелом.
  • G : утверждает позицию в конце предыдущего совпадения или в начале строки для первого совпадения
  • h (?!ENDb).*?(w{4,}) : Сопоставьте 1 пробел, за которым следует 0 или более символов, за которыми следует слово длиной 4 , которое записывается в группу # 1
  • (?=.*?bENDb) : Посмотрите вперед, чтобы подтвердить наличие слова END впереди

Комментарии:

1. да, это так, спасибо за ваш вклад. никогда раньше не слышал о G, регулярные выражения завораживают!

2. кстати, я нашел кое-что в вашем регулярном выражении: (если вы поместите не 4 буквы рядом с началом, они больше не совпадают…

3. круто, теперь это работает отлично. не могли бы вы, пожалуйста, объяснить мне разницу?

4. @anubhava Это не удается, если у вас должно быть несколько END якорей, например START one two three END one two END , будет совпадать one, two, END, one, two

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

Ответ №2:

Если поддерживается квантификатор в lookbehind, вы также можете использовать

 (?<=bSTARTs (?:w s )*?)w{3,}(?=(?:s w )*?s ENDb)
  

Объяснение

  • (?<= Положительный взгляд назад, утверждение, что слева
    • bSTARTs (?:w s )*? Начало сопоставления необязательно повторяется символами word и пробелов
  • ) Закрыть просмотр назад
  • w{3,} Сопоставьте 3 или более символов слова
  • (?= Позитивный взгляд, подтвердите, что справа
    • (?:s w )*?s ENDb Необязательно повторять символы пробелов и слов и совпадать с КОНЦОМ
  • ) Закрыть обзор

Демонстрация регулярных выражений

Комментарии:

1. вау, большое вам спасибо, спустя часы и часы я наконец-то могу понять свою ошибку! Я не добавлял дополнительный «не жадный» поиск слов в моем взгляде назад и ни в моем взгляде вперед! Теперь это ТАК ясно, не могу отблагодарить вас!

2. слишком плохо, что quantified lookbehind не поддерживается в Java? 🙁

3.@Carl Verret Это так, но вы должны указать конечный квантификатор вместо бесконечного квантификатора. Вы могли бы попробовать (?<=bSTARTs{1,10}(?:w s{1,10}){0,1000})w{3,}(?=(?:s w )*?s ENDb) regex101.com/r/MfNElT/1

4. вы правы! но {0,1000} находится на другой стороне

5. Попробуйте сделать его не жадным {0,1000}?

Ответ №3:

Это интересный сценарий. Обычно вам лучше извлечь строку start(.*)end из основного источника, а затем запустить регулярное выражение для подстроки.

Это не значит, что это невозможно сделать с одним регулярным выражением!

Я уверен, что вы боролись positive|negative lookahead|lookbehind и обнаружили, что это настоящая неприятность, с которой вы не можете выполнить динамический поиск длины, например (?<=start.*) , как вы можете lookahead .

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

Регулярное выражение

 (?:.*start|^.*|end.*)|b(w{3,})(?=.*end)
(?:                                         : Start of non-capture group
   .*start                                  : [Match pattern 1a] matches anything upto and including the {start} anchor
          |                                 : OR operator
           ^.*                              : [Match pattern 1b] matches from the {^} start of the string to the end
              |                             : OR operator
               end.*                        : [Match pattern 1c] matches from the {end} anchor to the end of the string
                    )                       : End of non-capture group
                     |                      : OR operator
                      b(w{3,})(?=.*end)   : Captures a boundary [b] followed by word characters [a-zA-Z0-9_] 3 or more times whilst using a positive lookahead to check that the {end} anchor hasn't been passed
  

Многословное объяснение

Приведенное выше регулярное выражение может быть записано простыми словами:

     NON-CAPTURING_GROUP OR CAPTURING_GROUP
OR, more verbose
    (MATCH_PATTERN_1a OR MATCH_PATTERN_1b OR MATCH_PATTERN_1c) OR MATCH_PATTERN_2
  
  • NON-CAPTURING_GROUP Всегда вычисляется первым, поэтому мы проверяем здесь, что мы действительно хотим получить совпадения
    • MATCH_PATTERN_1a проверяет, start присутствует ли привязка, и перемещает cursor эту точку в строке
    • MATCH_PATTERN_1b совпадения возможны только в случае 1a сбоя и наличия start привязки в строке. Если это так, оно соответствует всему, и выражение останавливается.
    • MATCH_PATTERN_1c проверяет, что точка end привязки не достигнута. Если оно есть, то оно соответствует концу строки, и выражение останавливается.
  • CAPTURING_GROUP Всегда вычисляется вторым; поэтому совпадает только в том случае, если оно должно
    • MATCH_PATTERN_2 соответствует любой границе слова, за которой следуют символы слова [a-zA-Z0-9_] между указанными длинами
      • Оно также проверяет с помощью a positive lookahead , чтобы убедиться end , что привязка не была передана

Предупреждение

Имейте в виду, что первый и последний захват всегда будут из NON-CAPTURE группы и должны игнорироваться. В зависимости от того, как реализовано регулярное выражение, оно может быть пустым, полной строкой соответствия или обоими (многомерный массив).

Пример [Python]

Примечание: Python выводится в формате $result = $full_matches[] Примечание: flag = re.I было установлено, чтобы сделать регулярное выражение нечувствительным к регистру, т.е. Оно соответствует START и start

 import re

test_str1 = """one two three four START four five two five six END seven"""
test_str2 = """this is the start some words are to be selected end no more select"""
test_str3 = """these are some words that shouldn't be selected end also not selected"""
test_str4 = """end two four five two five six END seven"""
test_str5 = """one start two three end four five six end seven one"""
test_str6 = """END START two four five two five six seven"""

regex1 = r"(?:.*start|^.*|end.*)|b(w{3,})(?=.*end)"
regex2 = r"(?:.*start|^.*|end.*)|b(w{4,})(?=.*end)"

print(re.findall(regex1, test_str1, re.I))
print(re.findall(regex1, test_str2, re.I))
print(re.findall(regex1, test_str3, re.I))
print(re.findall(regex1, test_str4, re.I))
print(re.findall(regex1, test_str5, re.I))
print(re.findall(regex1, test_str6, re.I))

print(re.findall(regex2, test_str1, re.I))      
print(re.findall(regex2, test_str2, re.I))
print(re.findall(regex2, test_str3, re.I))
print(re.findall(regex2, test_str4, re.I))
print(re.findall(regex2, test_str5, re.I))
print(re.findall(regex2, test_str6, re.I))

'''
  Output:
    ['', 'four', 'five', 'two', 'five', 'six', '']
    ['', 'some', 'words', 'are', 'selected', '']
    ['']
    ['']
    ['', 'two', 'three', '']
    ['']
    ['', 'four', 'five', 'five', '']
    ['', 'some', 'words', 'selected', '']
    ['']
    ['']
    ['', 'three', '']
    ['']
'''
  

Пример [PHP]

Примечание: PHP выводится в формате $result = [$full_matches[], $capture_group[]] Примечание: flag = i было установлено, чтобы сделать регулярное выражение нечувствительным к регистру, т.е. Оно соответствует START и start

 $test_str1 = "one two three four START four five two five six END seven";
$test_str2 = "this is the start some words are to be selected end no more select";
$test_str3 = "these are some words that shouldn't be selected end also not selected";
$test_str4 = "end two four five two five six END seven";
$test_str5 = "one start two three end four five six end seven one";
$test_str6 = "END START two four five two five six seven";

$regex1 = "/(?:.*start|^.*|end.*)|b(w{3,})(?=.*end)/i";
$regex2 = "/(?:.*start|^.*|end.*)|b(w{4,})(?=.*end)/i";

preg_match_all($regex1, $test_str1, $matches1);
preg_match_all($regex1, $test_str2, $matches2);
preg_match_all($regex1, $test_str3, $matches3);
preg_match_all($regex1, $test_str4, $matches4);
preg_match_all($regex1, $test_str5, $matches5);
preg_match_all($regex1, $test_str6, $matches6);

preg_match_all($regex2, $test_str1, $matches7);
preg_match_all($regex2, $test_str2, $matches8);
preg_match_all($regex2, $test_str3, $matches9);
preg_match_all($regex2, $test_str4, $matches10);
preg_match_all($regex2, $test_str5, $matches11);
preg_match_all($regex2, $test_str6, $matches12);

echo json_encode($matches1);
echo "n";
echo json_encode($matches2);
echo "n";
echo json_encode($matches3);
echo "n";
echo json_encode($matches4);
echo "n";
echo json_encode($matches5);
echo "n";
echo json_encode($matches6);
echo "n";
echo json_encode($matches7);
echo "n";
echo json_encode($matches8);
echo "n";
echo json_encode($matches9);
echo "n";
echo json_encode($matches10);
echo "n";
echo json_encode($matches11);
echo "n";
echo json_encode($matches12);

/*
  Output:
    [["one two three four START","four","five","two","five","six","END seven"],["","four","five","two","five","six",""]]
    [["this is the start","some","words","are","selected","end no more select"],["","some","words","are","selected",""]]
    [["these are some words that shouldn't be selected end also not selected"],[""]]
    [["end two four five two five six END seven"],[""]]
    [["one start","two","three","end four five six end seven one"],["","two","three",""]]
    [["END START"],[""]]
    [["one two three four START","four","five","five","END seven"],["","four","five","five",""]]
    [["this is the start","some","words","selected","end no more select"],["","some","words","selected",""]]
    [["these are some words that shouldn't be selected end also not selected"],[""]]
    [["end two four five two five six END seven"],[""]]
    [["one start","three","end four five six end seven one"],["","three",""]]
    [["END START"],[""]]
*/
  

.NET

Если вы использовали .NET , то регулярное выражение становится намного проще:

 start(s*(?!end)w s*)*end
  

Это потому .NET , что позволяет вам фиксировать все вхождения строки с отклонением.

Другие методы

На самом деле похоже, что вам было бы лучше разбить строку на подстроки и вычислить оттуда…

Ввод

 start one two three end start one two three end one two end
  

Разделение строк

 start(.*?)end

[0] => start one two three end
[1] => start one two three end one two end
  

Сопоставьте слова

 bw{3,}
  

Пример

 $string = "start one two three end start four five six end one two end";

preg_match_all('/start(.*?)end/i', $string, $matches);

foreach($matches[1] as $match){
  preg_match_all('/bw{3,}/', $match, $out);
  var_dump($out);
}

/*
  Output:
    array(1) {
      [0]=>
      array(3) {
        [0]=>
        string(3) "one"
        [1]=>
        string(3) "two"
        [2]=>
        string(5) "three"
      }
    }
    array(1) {
      [0]=>
      array(3) {
        [0]=>
        string(4) "four"
        [1]=>
        string(4) "five"
        [2]=>
        string(3) "six"
      }
    }
*/
  

Комментарии:

1. Это неправильно, потому что он будет печатать один и тот же вывод, даже если нет start слова, например four five two five six END seven

2. спасибо за пояснения и время, но, к сожалению, если вы удалите начальное слово, оно даже захватывает слова. Пришлось принять другой ответ.

3. @anubhava Я не уверен, что вы имеете в виду, удалите начальное слово откуда? Оно определенно не будет захватывать слова после окончания; оно будет захватывать слова с начала строки, если там не start было слова

4. @anubhava хорошо, я полагаю, вы правы… Я предполагал, что искомые данные будут релевантными. Тем не менее, это достаточно простое решение

5. @anubhava Я только что сделал это, и у меня все работает нормально?? (при условии, что вы правильно установили флаги, т.е. i )