Статьи

Clojure: все вещи, регулярные выражения

Недавно я занимался утилизацией веб-страниц с использованием 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"]

В итоге

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

Ссылка: Clojure: все вещи регулярно от нашего партнера JCG Марка Нидхэма в блоге Марка Нидхэма .