Статьи

Токенизация с использованием регулярных выражений

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

На примере реального мира часто упускается из виду особенность большинства механизмов регулярных выражений, заключающаяся в том, что подшаблоны могут быть полезны для относительно простого запуска токенизаторов . Проблема? Мне нужно было сопоставить слово любому из слов «Кантон», «Регион» или «Группа» в строке и выполнить последующее действие в зависимости от того, какое из них соответствует.

Имея дело с четырьмя основными языками в Швейцарии (немецким, французским, итальянским и английским), становится немного интереснее; «Кантон» переводится как «Кантон» на немецком языке и «Кантон» на итальянском языке, а «Регион» — «Regione» на итальянском языке. и группа — «Группа», «Группа» и «Группа» на немецком, французском и итальянском языках соответственно. Сложив их в три простых регулярных выражения, которые я имею;

  • Кантон: /cantone?|kanton/i Кантон /cantone?|kanton/i
  • Регион: /regione?/i
  • Группа: /groupe?|grupp(?:o|e)/i

Теперь, изучив некоторую входную строку, я мог бы попробовать проверить каждое из этих регулярных выражений отдельно от строки, но это а) неэффективно и б) может привести к увеличению длины кода. Вместо этого я делаю одно регулярное выражение, используя подэлементы: /(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))/i … затем выясняю, какой подэлемент соответствует после совпадения сделан.

Обратите внимание, что технически эта проблема на самом деле не является проблемой токенизации, а скорее просто классифицирует входные данные с общим именем, но метод может быть довольно легко расширен. В PHP решение предоставлено, например, третьим аргументом preg_match () ;

$inputs = array( 'Kanton Zuerich', 'Frauenfeld Regione', 'Fricktal Gruppe'); foreach ( $inputs as $input ) { preg_match("/(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))/i", $input, $matches); print_r($matches); }
$inputs = array( 'Kanton Zuerich', 'Frauenfeld Regione', 'Fricktal Gruppe'); foreach ( $inputs as $input ) { preg_match("/(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))/i", $input, $matches); print_r($matches); } 

Я получаю вывод, как;

 Array ( [0] => Kanton [1] => Kanton ) Array ( [0] => Regione [1] => [2] => Regione ) Array ( [0] => Gruppe [1] => [2] => [3] => Gruppe ) 

Обратите внимание, как первый элемент этого массива всегда совпадает с тем, что элементы с индексом 1+ соответствуют позиции подшаблона, с которым я сопоставляюсь, слева направо в шаблоне — это я могу использовать, чтобы сказать мне, что я на самом деле сопоставил, например;

$inputs = array( 'Kanton Zuerich', 'Frauenfeld Regione', 'Fricktal Gruppe'); $tokens = array('canton','region','group'); // the token names foreach ( $inputs as $input ) { if ( preg_match("/(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))/i", $input, $matches) ) { foreach ( array_keys( $matches) as $key) { if ( $key == 0 ) { continue; } // skip the first element // Look for the subpattern we matched... if ( $matches[$key] != "" ) { printf("Input: '%s', Token: '%s'n", $input, $tokens[$key-1]); } } } }
$inputs = array( 'Kanton Zuerich', 'Frauenfeld Regione', 'Fricktal Gruppe'); $tokens = array('canton','region','group'); // the token names foreach ( $inputs as $input ) { if ( preg_match("/(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))/i", $input, $matches) ) { foreach ( array_keys( $matches) as $key) { if ( $key == 0 ) { continue; } // skip the first element // Look for the subpattern we matched... if ( $matches[$key] != "" ) { printf("Input: '%s', Token: '%s'n", $input, $tokens[$key-1]); } } } } 

Что дает мне вывод, как;

 Input: 'Kanton Zuerich', Token: 'canton' Input: 'Frauenfeld Regione', Token: 'region' Input: 'Fricktal Gruppe', Token: 'group' 

… Так что теперь я могу классифицировать входные данные для одного из известных токенов и реагировать соответствующим образом. Большинство регулярных выражений. apis предоставляет что-то в этом роде, например, здесь то же самое (и намного чище) в Python, который я и использовал в этой проблеме;

import re p = re.compile('(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))', re.I) inputs = ('Kanton Zuerich', 'Frauenfeld Regione', 'Fricktal Gruppe') tokens = ('canton','region','group') for input in inputs: m = p.search(input) if not m: continue for group, token in zip(m.groups(), tokens): if group is not None: print "Input: '%s', Token: '%s'" % ( input, token )
import re p = re.compile('(cantone?|kanton)|(regione?)|(groupe?|grupp(?:o|e))', re.I) inputs = ('Kanton Zuerich', 'Frauenfeld Regione', 'Fricktal Gruppe') tokens = ('canton','region','group') for input in inputs: m = p.search(input) if not m: continue for group, token in zip(m.groups(), tokens): if group is not None: print "Input: '%s', Token: '%s'" % ( input, token ) 

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

Альтернативная проблема, чтобы дать вам понять, как этот метод может быть применен. Допустим, вы хотите проанализировать HTML-документ и перечислить подмножество уровня блока против встроенных тегов уровня, которые он содержит. Вы можете сделать это с двумя подэлементами, например (</?(?:div|h[1-6]{1}|p|ol|ul|pre).*?>)|(</?(?:span|code|em|strong|a).*?>) (обратите внимание, что это регулярное выражение «как есть» связано с идеей жадности в Python — вам нужно изменить его на PHP), что приводит к чему-то вроде этого — python;

p = re.compile('(</?(?:div|h[1-6]{1}|p|ol|ul|pre).*?>)|(</?(?:span|code|em|strong|a).*?>)') for match in p.finditer('foo <div> test <strong>bar</strong> test 1</div> bar'): print "[pos: %s] matched %s" % ( match.start(), str(match.groups()) )
p = re.compile('(</?(?:div|h[1-6]{1}|p|ol|ul|pre).*?>)|(</?(?:span|code|em|strong|a).*?>)') for match in p.finditer('foo <div> test <strong>bar</strong> test 1</div> bar'): print "[pos: %s] matched %s" % ( match.start(), str(match.groups()) ) 

Вызов match.groups() возвращает кортеж, который сообщает вам, какой match.start() совпал, в то время как match.start() сообщает вам позицию символа в документе, где было match.start() совпадение, позволяя вам извлечь подстроки из документа.