Como posso lidar eficientemente com pesquisa múltipla Perl / substituir operações na mesma corda?

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

  •  20-08-2019
  •  | 
  •  

Pergunta

Então, o meu script Perl, basicamente, recebe uma string e, em seguida, tenta limpá-lo, fazendo pesquisa múltipla e substitui nele, assim:

$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...

Como você pode ver, eu estou lidando com html desagradável e tem que vencê-lo em sua apresentação.

Eu estou esperando que existe uma maneira mais simples e esteticamente atraente para fazer isso. Eu tenho cerca de 50 linhas que olham apenas como o que está acima.

Eu ter resolvido uma versão deste problema usando um hash onde a chave é o comentário, eo hash é a expressão reg, assim:

%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]\.'
}

E é assim que eu usá-lo:

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

O problema surge quando eu tentar minha mão em um hash que onde a chave é a expressão e aponta para o que eu quero substituí-lo com ... e há um US $ 1 ou US $ 2 nele.

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

A descrição acima é para fazer isso:

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

Mas eu não consigo passar a parte de "$ 1" para o regex literalmente (. Acho que essa é a palavra certa ... parece que o $ 1 está sendo interpretado, embora eu usei 'marcas) Então, isso resulta em:

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

E isso não funciona.

Então 2 perguntas:

Fácil:? Como posso lidar com um grande número de de regex de uma forma facilmente editável para que eu possa mudar e adicioná-los sem justa cortar e colar a linha antes

Mais difícil: Como posso lidar com eles usando um hash (ou matriz se eu tiver, digamos, várias peças que deseja incluir, como 1) parte de pesquisa, 2) a substituição 3) comentário, 4) caso / global modificadores insensíveis ), se essa é de fato a maneira mais fácil de fazer isso?

Obrigado por sua ajuda -

Foi útil?

Solução

Problema # 1

Como lá não parecem ser muito estrutura compartilhada pelas expressões regulares individuais, não há realmente uma maneira mais simples ou mais claro do que apenas listando os comandos como você fez. Uma abordagem comum para a diminuição da repetição no código como este é mover $text em $_, de modo que em vez de ter que dizer:

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

Você pode simplesmente dizer:

s/foo/bar/g;

Um idioma comum para fazer isso é usar um loop for() degenerada como um topicalizer:

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

O escopo deste bloco irá preservar qualquer valor pré-existente de $_, por isso não há necessidade de local explicitamente $_ize.

Neste ponto, você eliminou quase todos os personagens não-clichê - como muito mais curto que pode obter, mesmo em teoria

A menos que você realmente quer (como o seu problema # 2 sugere) é melhorada modularidade , por exemplo, a capacidade de interagir sobre, relatório sobre, conte etc. todas as expressões regulares.

Problema # 2

Você pode usar a sintaxe qr// citar a "busca" parte da substituição:

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

No entanto, eu não sei de uma maneira de citar a parte "substituição" de forma adequada. Eu esperava que qr// iria trabalhar para isso também, mas isso não acontece. Há duas alternativas vale a pena considerar:

1. Use eval() em seu loop foreach. Isto permitir-lhe manter o seu actual de hash %rxcheck2. Desvantagem:. Você deve sempre se preocupar com a segurança com eval()s cordas

2. Use um conjunto de sub-rotinas anônimos:

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

Você poderia naturalmente usar um hash em vez com alguma chave mais útil como o hash, e / ou você poderia usar elementos de valor múltiplo (ou valores de hash), incluindo comentários ou outras informações.

Outras dicas

Você diz que você está lidando com HTML. Você agora estão percebendo que este bonito é muito uma batalha perdida com passageiro e soluções frágeis.

Um analisador HTML adequada seria facilitar a sua vida. HTML :: Parser pode ser difícil de usar, mas existem outros muito bibliotecas úteis sobre CPAN que eu posso recomendar se você pode especificar o que você está tentando fazer ao invés de como .

Hashes não são bons porque eles são não-ordenada. I encontrar uma variedade de matrizes cujos variedade segunda contém uma regex compilado e uma corda para eval (na verdade, é um eval duplo) funciona melhor:

#!/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";

Eu acho segunda solução de j_random_hacker é muito superior ao meu. sub-rotinas individuais dar-lhe mais flexibilidade e são uma ordem de magnitude mais rápido do que a minha solução /ee:

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

Aqui está o código que produz esses números:

#!/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;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top