Недавно я занимался утилизацией веб-страниц с использованием Clojure и Enlive, и мне пришлось писать регулярные выражения для извлечения данных, которые меня интересуют.
В своих путешествиях я сталкивался с несколькими различными функциями, и я никогда не был уверен, какая из них правильная, поэтому я решил, что буду документировать то, что пытался сделать для меня в будущем.
Проверьте, соответствует ли регулярное выражение
Первое регулярное выражение, которое я написал, было при отборе результатов Лиги чемпионов из Статистического фонда Rec.Sport.Soccer, и я хотел определить, какие пролеты содержали результат матча, а какие — нет.
Соответствующая строка будет выглядеть так:
1
|
Real Madrid-Juventus Turijn 2 - 1 |
И несоответствующий, как это:
1
|
53 ’Nedved 0 - 1 , 66 'Xavi Hernández 1 - 1 , 114 ’Zalayeta 1 - 2 |
Я написал следующее регулярное выражение для определения результатов матчей:
1
|
[a-zA-Z\s]+-[a-zA-Z\s]+ [ 0 - 9 ][\s]?.[\s]?[ 0 - 9 ] |
Затем я написал следующую функцию, используя повторные совпадения, которые возвращали бы true или false в зависимости от ввода:
1
2
|
(defn recognise-match? [row] (not (clojure.string/blank? (re-matches # "[a-zA-Z\s]+-[a-zA-Z\s]+ [0-9][\s]?.[\s]?[0-9]" row)))) |
1
2
3
4
|
> (recognise-match? "Real Madrid-Juventus Turijn 2 - 1" ) true > (recognise-match? "53’Nedved 0-1, 66'Xavi Hernández 1-1, 114’Zalayeta 1-2" ) false |
Повторные совпадения возвращают совпадения только в том случае, если вся строка соответствует шаблону, что означает, что если бы у нас была строка с каким-то поддельным текстом после оценки, она не будет соответствовать:
1
2
|
> (recognise-match? "Real Madrid-Juventus Turijn 2 - 1 abc" ) false |
Если мы не возражаем против этого и хотим, чтобы какая-то часть строки соответствовала нашему шаблону, мы можем вместо этого использовать re-find :
1
2
|
(defn recognise-match? [row] (not (clojure.string/blank? (re-find # "[a-zA-Z\s]+-[a-zA-Z\s]+ [0-9][\s]?.[\s]?[0-9]" row)))) |
1
2
|
> (recognise-match? "Real Madrid-Juventus Turijn 2 - 1 abc" ) true |
Извлечь группы захвата
Следующее, что я хотел сделать, это захватить команды и счет в матче, который я первоначально сделал, используя re-seq :
1
2
|
> (first (re-seq # "([a-zA-Z\s]+)-([a-zA-Z\s]+) ([0-9])[\s]?.[\s]?([0-9])" "FC Valencia-Internazionale Milaan 2 - 1" )) [ "FC Valencia-Internazionale Milaan 2 - 1" "FC Valencia" "Internazionale Milaan" "2" "1" ] |
Затем я извлек различные части следующим образом:
01
02
03
04
05
06
07
08
09
10
|
> (def result (first (re-seq # "([a-zA-Z\s]+)-([a-zA-Z\s]+) ([0-9])[\s]?.[\s]?([0-9])" "FC Valencia-Internazionale Milaan 2 - 1" ))) > result [ "FC Valencia-Internazionale Milaan 2 - 1" "FC Valencia" "Internazionale Milaan" "2" "1" ] > (nth result 1 ) "FC Valencia" > (nth result 2 ) "Internazionale Milaan" |
re-seq возвращает список, который содержит последовательные совпадения регулярного выражения. Список будет содержать либо строки, если мы не укажем группы захвата, либо вектор, содержащий соответствующий шаблон и каждую из групп захвата.
Например, если мы теперь сопоставим только последовательности AZ или пробелов и удалим оставшуюся часть шаблона сверху, мы получим следующие результаты:
1
2
3
4
5
|
> (re-seq # "([a-zA-Z\s]+)" "FC Valencia-Internazionale Milaan 2 - 1" ) ([ "FC Valencia" "FC Valencia" ] [ "Internazionale Milaan " "Internazionale Milaan " ] [ " " " " ] [ " " " " ]) > (re-seq # "[a-zA-Z\s]+" "FC Valencia-Internazionale Milaan 2 - 1" ) ( "FC Valencia" "Internazionale Milaan " " " " " ) |
В нашем случае повторный поиск или повторное сопоставление на самом деле имеет больше смысла, поскольку мы хотим сопоставить шаблон только один раз. Если после этого есть другие совпадения, они не включаются в результаты. например
1
2
3
4
5
|
> (re-find # "[a-zA-Z\s]+" "FC Valencia-Internazionale Milaan 2 - 1" ) "FC Valencia" > (re-matches # "[a-zA-Z\s]*" "FC Valencia-Internazionale Milaan 2 - 1" ) nil |
повторное совпадение возвращает здесь ноль, потому что в строке есть символы, которые не соответствуют шаблону, то есть дефис между двумя значениями.
Если мы свяжем это с нашими группами захвата, мы получим следующее:
01
02
03
04
05
06
07
08
09
10
11
|
> (def result (re-find # "([a-zA-Z\s]+)-([a-zA-Z\s]+) ([0-9])[\s]?.[\s]?([0-9])" "FC Valencia-Internazionale Milaan 2 - 1" )) > result [ "FC Valencia-Internazionale Milaan 2 - 1" "FC Valencia" "Internazionale Milaan" "2" "1" ] > (nth result 1 ) "FC Valencia" > (nth result 2 ) "Internazionale Milaan" |
Я также натолкнулся на функцию re-pattern, которая предоставляет более подробный способ создания шаблона, а затем оценил его с помощью re-find :
1
2
|
> (re-find (re-pattern "([a-zA-Z\\s]+)-([a-zA-Z\\s]+) ([0-9])[\\s]?.[\\s]?([0-9])" ) "FC Valencia-Internazionale Milaan 2 - 1" ) [ "FC Valencia-Internazionale Milaan 2 - 1" "FC Valencia" "Internazionale Milaan" "2" "1" ] |
Одно из отличий состоит в том, что мне пришлось избежать специальной последовательности ‘\ s’, иначе я получил следующее исключение:
1
|
RuntimeException Unsupported escape character: \s clojure.lang.Util.runtimeException (Util.java: 170 ) |
Я тоже хотел поиграть с регруппами, но это, казалось, вызывало исключение довольно часто, когда я ожидал, что это сработает.
Последней функцией, которую я посмотрел, был re-matcher, который, казалось, был длинным указателем для синтаксиса «#» », использованного ранее в посте для определения соответствий:
1
2
|
> (re-find (re-matcher # "([a-zA-Z\s]+)-([a-zA-Z\s]+) ([0-9])[\s]?.[\s]?([0-9])" "FC Valencia-Internazionale Milaan 2 - 1" )) [ "FC Valencia-Internazionale Milaan 2 - 1" "FC Valencia" "Internazionale Milaan" "2" "1" ] |
В итоге
Таким образом, в заключение я думаю, что большинство случаев использования охватываются повторным поиском и повторным совпадением и, возможно, повторными запросами в особых случаях. Я не мог видеть, где бы я использовал другие функции, но я счастлив, что оказался неправ.