#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/14. вы правы! но {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
, что привязка не была передана
- Оно также проверяет с помощью a
Предупреждение
Имейте в виду, что первый и последний захват всегда будут из 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
)