Как эффективно обрабатывать несколько операций поиска / замены Perl в одной строке? - PullRequest
5 голосов
/ 09 мая 2009

Итак, мой Perl-скрипт в основном берет строку, а затем пытается очистить ее, выполнив многократный поиск и заменив ее, например:

$text =~ s/<[^>]+>/ /g;
$text =~ s/\s+/ /g;
$text =~ s/[\(\{\[]\d+[\(\{\[]/ /g;
$text =~ s/\s+[<>]+\s+/\. /g;
$text =~ s/\s+/ /g;
$text =~ s/\.*\s*[\*|\#]+\s*([A-Z\"])/\. $1/g; # replace . **** Begin or . #### Begin or ) *The 
$text =~ s/\.\s*\([^\)]*\) ([A-Z])/\. $1/g; # . (blah blah) S... => . S...

Как вы видите, я имею дело с неприятным html и должен превзойти его.

Я надеюсь, что есть более простой, эстетически привлекательный способ сделать это. У меня есть около 50 строк, которые выглядят так же, как и выше.

Я решил одну версию этой проблемы, используя хеш, где ключом является комментарий, а хешем является выражение reg, например:

%rxcheck = (
        'time of day'=>'\d+:\d+', 
    'starts with capital letters then a capital word'=>'^([A-Z]+\s)+[A-Z][a-z]',
    'ends with a single capital letter'=>'\b[A-Z]\.'
}

И вот как я это использую:

 foreach my $key (keys %rxcheck) {
if($snippet =~ /$rxcheck{ $key }/g){ blah blah  }
 }

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

%rxcheck2 = (
        '(\w) \"'=>'$1\"'
}

Выше указано это для:

$snippet =~ s/(\w) \"/$1\"/g;

Но я не могу передать часть $ 1 в регулярное выражение (я думаю, что это правильное слово ... кажется, что $ 1 интерпретируется, хотя я использовал знаки '). Таким образом, это приводит к:

if($snippet =~ /$key/$rxcheck2{ $key }/g){  }

И это не работает.

Итак, 2 вопроса:

Легко: Как мне обработать большое количество регулярных выражений в легко редактируемом виде, чтобы я мог изменять и добавлять их, просто не обрезая и не вставляя строку раньше?

Сложнее: как мне обрабатывать их, используя хеш (или массив, если у меня есть, скажем, несколько частей, которые я хочу включить, например, 1) часть для поиска, 2) замена 3) комментарий, 4) глобальные / нечувствительные к регистру модификаторы ), если это на самом деле самый простой способ сделать это?

Спасибо за вашу помощь -

Ответы [ 3 ]

10 голосов
/ 09 мая 2009

Задача № 1

Поскольку отдельные регулярные выражения, по-видимому, не имеют большой структуры, на самом деле нет более простого или более ясного способа, чем просто перечисление команд, как вы это сделали. Один из распространенных подходов к уменьшению повторения в таком коде - переместить $text в $_, чтобы вместо необходимости говорить:

$text =~ s/foo/bar/g;

Вы можете просто сказать:

s/foo/bar/g;

Распространенная идиома для этого заключается в использовании вырожденного цикла for() в качестве средства актуализации:

for($text)
{
  s/foo/bar/g;
  s/qux/meh/g;
  ...
}

Область действия этого блока сохранит любое существующее значение $_, поэтому нет необходимости явно local ize $_.

К этому моменту вы убрали почти все не стандартные символы - насколько он может быть короче, даже в теории?

Если то, что вы действительно хотите (как предполагает ваша проблема №2), не улучшено модульность , например, возможность выполнять итерации, создавать отчеты, подсчитывать и т. Д. Все регулярные выражения.

Задача № 2

Вы можете использовать синтаксис qr//, чтобы заключить в кавычку часть поиска:

my $search = qr/(<[^>]+>)/;
$str =~ s/$search/foo,$1,bar/;

Однако я не знаю, как адекватно процитировать «замену». Я надеялся, что qr// тоже подойдет для этого, но это не так. Есть две альтернативы, которые стоит рассмотреть:

1. Используйте eval() в вашем цикле foreach. Это позволит вам сохранить текущий хэш %rxcheck2. Недостаток: вы всегда должны быть обеспокоены безопасностью строки eval() s.

2. Используйте массив анонимных подпрограмм:

my @replacements = (
    sub { $_[0] =~ s/<[^>]+>/ /g; },
    sub { $_[0] =~ s/\s+/ /g; },
    sub { $_[0] =~ s/[\(\{\[]\d+[\(\{\[]/ /g; },
    sub { $_[0] =~ s/\s+[<>]+\s+/\. /g },
    sub { $_[0] =~ s/\s+/ /g; },
    sub { $_[0] =~ s/\.*\s*[\*|\#]+\s*([A-Z\"])/\. $1/g; },
    sub { $_[0] =~ s/\.\s*\([^\)]*\) ([A-Z])/\. $1/g; }
);

# Assume your data is in $_
foreach my $repl (@replacements) {
    &{$repl}($_);
}

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

4 голосов
/ 09 мая 2009

Вы говорите, что имеете дело с HTML. Теперь вы понимаете, что это в значительной степени проигрышная битва с мимолетными и хрупкими решениями.

Правильный анализатор HTML сделает вашу жизнь проще. HTML :: Parser может быть сложно использовать, но есть и другие очень полезные библиотеки на CPAN , которые я могу порекомендовать, если вы можете указать что вы пытаетесь сделать а не как .

4 голосов
/ 09 мая 2009

Хеши не хороши, потому что они неупорядочены. Я нахожу массив массивов, второй массив которых содержит скомпилированное регулярное выражение и строку для оценки (на самом деле это двойное вычисление) работает лучше всего:

#!/usr/bin/perl

use strict;
use warnings;

my @replace = (
    [ qr/(bar)/ => '"<$1>"' ],
    [ qr/foo/   => '"bar"'  ],
);

my $s = "foo bar baz foo bar baz";

for my $replace (@replace) {
    $s =~ s/$replace->[0]/$replace->[1]/gee;
}

print "$s\n";

Я думаю, что второе решение j_random_hacker значительно превосходит мое. Отдельные подпрограммы обеспечивают максимальную гибкость и на порядок быстрее, чем мое решение /ee:

bar <bar> baz bar <bar> baz
bar <bar> baz bar <bar> baz
         Rate refs subs
refs  10288/s   -- -91%
subs 111348/s 982%   --

Вот код, который производит эти числа:

#!/usr/bin/perl

use strict;
use warnings;

use Benchmark;

my @subs = (
    sub { $_[0] =~ s/(bar)/<$1>/g },
    sub { $_[0] =~ s/foo/bar/g },
);

my @refs = (
    [ qr/(bar)/ => '"<$1>"' ],
    [ qr/foo/   => '"bar"'  ],
);

my %subs = (
    subs => sub {
        my $s = "foo bar baz foo bar baz";
        for my $sub (@subs) {
            $sub->($s);
        }
        return $s;
    },
    refs => sub {
        my $s = "foo bar baz foo bar baz";
        for my $ref (@refs) {
            $s =~ s/$ref->[0]/$ref->[1]/gee;
        }
        return $s;
    }
);

for my $sub (keys %subs) {
    print $subs{$sub}(), "\n";
}

Benchmark::cmpthese -1, \%subs;
...