Существует множество ресурсов по регулярным выражениям, но неоспоримым лидером, конечно же, является 'Mastering Regular Expressions' Дж. Фридла, которую стоит прочитать, с которой стоит не соглашаться и, в конце концов, по которой стоит учиться. Ряд вопросов, которые здесь будут затронуты, касаются в определённой степени самого Юникода, как явления необычайно важного, и то, как регулярные выражения в Perl могут помочь в их решении.
FYI: perldoc perlunicode - Perl Unicode support
Чтобы быть достаточно подготовленным для прочтения данного материала читатель должен уверенно себя чувствовать в вопросах работы регулярных выражений и особенно в части, которая касается построения объектов регулярных выражений; а также, как внедрять исполняемый код в регулярные выражения в ходе замены или при поиске совпадений, чтобы динамически выполнять куски кода внутри m// или s///. Было бы неплохо, если бы читатель уже имел некоторый опыт работы с Юникодом и знал его организацию, и что при использовании всех вышеупомянутых предметов необходимо использовать 'use utf8' либо 'use encoding "utf8"' вместе с ещё одной прагмой - 'use re "eval"', которая разрешает внедрять исполняемый код в регулярные выражения. Всё это, естественно, само собой подразумевает, что читатель знаком с поведением '\w' в среде Юникода.
И последнее перед тем, как приступить к повествованию: 'use locale' накладывает гранитную глыбу на кросс-платформенность ваших приложений, в особенности, когда у вас присутствует что-то типа '\p{InCyrillic}' или 'p{InCJKCompatibilityIdeographs}', поскольку эти «звери» становятся чрезвычайно чувствительны к настройкам системы в регулярных выражениях и в контекстах 'use bytes' и 'no bytes'.
FYI: perldoc utf8 - Perl pragma to enable/disable UTF-8 (or UTF-EBCDIC) in source code
Чтобы покончить с введением, давайте освежим в памяти несколько базовых функций, которые могут помочь в наполнении вакуума о Юникоде:
#!/usr/bin/perl
use strict;
use encoding 'utf8';
our ( @packed, );
@packed = map{chr($_)} (0x4E00 .. 0x9FFF);
print $_, "\n" for @packed[0 .. 9];
exit(0);этот малыш может сгенерировать практически мгновенно более 20.000 иероглифов, которые используются в китайском, японском, корейском и, возможно, в старо-вьетнамском языках. Строка:
@packed = map{chr($_)} (0x4E00 .. 0x9FFF);
может быть совсем безболезненно переписана как:
@packed = map{pack 'U', $_} (0x4E00 .. 0x9FFF);
Здесь функции 'pack' и 'chr' несомненно взаимозаменяемы.
Теперь попробуем сделать что-либо более полезное. Поскольку Perl сам имеет встроенную поддержку Юникода нам не понадобится даже создание временных переменных и мы сможем сразу перейти к созданию нужных нам объектов регулярных выражений. Если у кого-либо из читателей возникнет вопрос по поводу присутствия китайских предложений, то можно ответить, что это потому, что постольку данный язык (а точнее CJK) имеет наибольшее присутствие в Юникоде в виде символов:
#!/usr/bin/perl
use strict;
use utf8;
use encoding 'utf8';
our ( $re, $sample );
$re = qr{(
[\p{InCJKUnifiedIdeographs}] |
[\p{InCJKCompatibility}] |
[\p{InCJKCompatibilityForms}] |
[\p{InCJKRadicalsSupplement}] |
[\p{InCJKCompatibilityIdeographsSupplement}] |
[\p{InCJKUnifiedIdeographsExtensionA}] |
[\p{InCJKCompatibilityIdeographs}] |
[\p{InCJKUnifiedIdeographsExtensionB}])}ox;
$sample = '五行: 一曰水, 二曰火, 三曰木, 四曰金, 五曰土
пять стихий: первая называется вода,
вторая — огонь, третья — дерево,
четвёртая — металл, пятая — земля
Five Elements: first is Water, second is Fire,
third is Wood, fourth is Metal, fifth is Earth';
while($sample =~ m/$re/g)
{
print $1,"\n";
}
exit(0);
очевидно, что в примере используются строки-образцы, содержащие текст на трёх языках и Perl совершенно точно (как и было ему велено) выбрал лишь те, которые принадлежат группе CJK.
Для тех, кому не понятно присутствие латинского «быка» в конце объекта регулярного выражения, мы рекомендуем обратиться к 'perldoc perlre'; ну, а для тех «книжных червей», которые желают знать всё обо всём, имеется несколько вопросов:
(1) откуда берётся значение для '$1'?
(2) в чём разница между 'use utf8' и 'use encoding "utf8"'? (то есть, точнее, почему они обе здесь?)
(3) почему модификаторы 'ox' и 'g' здесь разделены? Если у Вас готовы ответы на все эти вопросы, мы все как один снимаем перед Вами шляпы!
Настало время прибавить газу, или внести больше динамики в наши иллюстрации к регулярным выражениям. Взгляните на это:
#!/usr/bin/perl
use strict;
use utf8;
use encoding 'utf8';
use re 'eval';
our ( $re, $index, $num, $line, $key1, $key2,
@estimate, @over, @under, %got, );
$re = qr{(
[\p{InCJKUnifiedIdeographs}] |
[\p{InCJKCompatibility}] |
[\p{InCJKCompatibilityForms}] |
[\p{InCJKRadicalsSupplement}] |
[\p{InCJKCompatibilityIdeographsSupplement}] |
[\p{InCJKUnifiedIdeographsExtensionA}] |
[\p{InCJKCompatibilityIdeographs}] |
[\p{InCJKUnifiedIdeographsExtensionB}])}ox;
@over = qw(估計過高 переоценивать overestimate);
@under = qw(估計過低 недооценивать underestimate);
@estimate = (@over, @under);
$index = $num = 0;
grep{
$num++;
($line) = $_;
$got{$num}{s/$re/$index++/e} = $line if(/$re/);
} @estimate;
foreach $key1 ( sort {$a <=> $b} keys %got )
{
print "\n\nelement number:\t", $key1, "\n";
foreach $key2 ( sort {$a <=> $b} keys %{ $got{ $key1 } } )
{
print "match number: \t", $key2, "\t",
$got{ $key1 }{ $key2 }, "\n";
}
}
exit(0);
в скрипте присутствует определённая избыточность, но это исключительно в образовательных целях и, к тому же, в данном случае это никоим образом не влияет на КПД: попробуйте изобразить что-то типа этого, используя if/else управляющие структуры и сравните время выполнения. Что особенного в этих сорока строках? Есть пару вещей, которые заслуживают внимания:
(1)использование 'our'; как вы уже заметили, даёт возможность не передавать и не принимать никаких данных: всё решается за счёт создания хэша '%got', который программа вызывает и использует там, где надо;
(2)имеется инкремент счётчика '$index' по условию, который и возвращает номера элементов, отвечающих условию совпадения.
Другим способом подобного мышления является использование внедрённого кода; несмотря на то, что это выглядит примерно так, как и выше, но это не только иной вариант применения регулярных выражений, а иной способ построения условий, которые внедряются в них:
#!/usr/bin/perl
use strict;
use utf8;
use encoding 'utf8';
use re 'eval';
our ( $re, $index, $num, $sample1, $sample2,
$sample3, %src, %got, );
$re = qr{(
[\p{InCJKUnifiedIdeographs}] |
[\p{InCJKCompatibility}] |
[\p{InCJKCompatibilityForms}] |
[\p{InCJKRadicalsSupplement}] |
[\p{InCJKCompatibilityIdeographsSupplement}] |
[\p{InCJKUnifiedIdeographsExtensionA}] |
[\p{InCJKCompatibilityIdeographs}] |
[\p{InCJKUnifiedIdeographsExtensionB}])}ox;
$sample1 = q[一萬一千一百一十一 11111];
$sample2 = q[天一地二天三地四
небу соответствует число один,
земле — два, небу— три,
земле — четыре... (и т. д.; «Ицзин»)
one belongs to the Heaven,
two belongs to the Earth,
three belongs to the Heaven,
four belongs to the Earth... (etc, "YiJing")];
$sample3 =q[道生一, 一生二, 二生三, 三生萬物
Дао рождает одно (нерасчленённое единство),
одно рождает два (раздвоенность),
два рождает три (триаду),
от трёх рождаются все существа (вещи)
Dao bears one (indivisible unity),
one gives birth to the two (duality),
two produces three (triad),
those three flood everything (all things in the world)];
grep{ $src{$index++} = $_} ($sample1, $sample2, $sample3);
grep{
grep{
m#($re) ? (??{$got{$_}++ if($1 ne '')})#cgx;
} split //, $src{$_};
} sort keys %src;
foreach $num ( keys %got )
{
print 'the match: ', "\t", $num, ' was seen:', "\t",
$got{ $num }, " time(s)\n";
}
exit(0);
Построение хэша '%src' является несколько искусственным здесь, если сравнивать с мощью хэшей вообще, и в этом примере как нельзя лучше показан способ применения хэшей, точнее, «прозрачность» их использования через всю программу. Другой интересной точкой является функция 'split', которая действительно полностью «понимает» Юникод. Есть и другие функции с подобным поведением в Юникодовской среде, например: cho(m)p; но надо помнить, что не всё гладко в этом отношении. Чтобы понять это попробуйте поиграть с 'use/no bytes' и 'length'.
0 коммент.:
Отправить комментарий