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

StackOverflow https://stackoverflow.com/questions/843542

  •  20-08-2019
  •  | 
  •  

Вопрос

Итак, мой 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 строк, которые выглядят так же, как показано выше.

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

%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) глобальные/регистронезависимые модификаторы), если это так на самом деле самый простой способ это сделать?

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

Это было полезно?

Решение

Проблема №1

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

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

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

s/foo/bar/g;

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

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

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

На данный момент вы удалили почти все нестандартные символы — насколько короче они могут стать даже в теории?

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

Проблема №2

Вы можете использовать qr// синтаксис для цитирования «поисковой» части подстановки:

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

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

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

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}($_);
}

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

Другие советы

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

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

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

#!/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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top