Como posso usar Perl para personagens Intercale entre jogos consecutivos com uma substituição regex?
-
22-07-2019 - |
Pergunta
As seguintes linhas de valores separados por vírgulas contém vários campos vazios consecutivos:
$rawData =
"2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,,Clear\n
2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,,,\n"
Eu quero substituir esses campos vazios com valores 'N / A', que é por isso que eu decidi fazê-lo através de uma substituição regex.
Eu tentei isso antes de tudo:
$rawdata =~ s/,([,\n])/,N\/A/g; # RELABEL UNAVAILABLE DATA AS 'N/A'
que retornou
2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,N/A,Clear\n
2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,N/A,,N/A,\n
Não é o que eu queria. O problema ocorre quando ocorrem mais de duas vírgulas consecutivas. Os GOBBLES regex até duas vírgulas de cada vez, por isso começa na terceira vírgula ao invés da segunda quando se examina novamente a corda.
Eu pensei que isso poderia ser algo a ver com a verificação à frente vs. lookback afirmações, então eu tentei o seguinte regex out:
$rawdata =~ s/(?<=,)([,\n])|,([,\n])$/,N\/A$1/g; # RELABEL UNAVAILABLE DATA AS 'N/A'
que resultou em:
2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,,N/A,Clear\n
2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,N/A,,N/A,,N/A,,N/A\n
Isso não quer trabalhar. Ele só mudou a vírgula-pares por um.
Eu sei que lavar essa string através do mesmo regex duas vezes irá fazê-lo, mas que parece bruto. Certamente, deve haver uma maneira de obter uma única substituição regex para fazer o trabalho. Alguma sugestão?
A seqüência final deve ficar assim:
2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,N/A,N/A,Clear\n
2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,N/A,,N/A,N/A,N/A,N/A,N/A\n
Solução
Eu não conseguia entender o que você estava tentando fazer no seu exemplo lookbehind, mas eu suspeito que você está sofrendo de um erro de precedência lá, e que tudo após o lookbehind deve ser encerrado num (?: ... )
para que o doesn |
' t Evite fazer o lookbehind.
Partindo do zero, o que você está tentando fazer sons bastante simples: coloque N / A após uma vírgula se for seguido por outro vírgula ou uma nova linha:
s!,(?=[,\n])!,N/A!g;
Exemplo:
my $rawData = "2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,,Clear\n2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,,,\n";
use Data::Dumper;
$Data::Dumper::Useqq = $Data::Dumper::Terse = 1;
print Dumper($rawData);
$rawData =~ s!,(?=[,\n])!,N/A!g;
print Dumper($rawData);
Output:
"2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,,Clear\n2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,,,\n"
"2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,N/A,Clear\n2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,N/A,N/A,N/A,N/A\n"
Outras dicas
EDIT: Note que você poderia abrir uma filehandle para a cadeia de dados e deixe negócio readline
com fins de linha:
#!/usr/bin/perl
use strict; use warnings;
use autodie;
my $str = <<EO_DATA;
2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,,Clear
2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,,,
EO_DATA
open my $str_h, '<', \$str;
while(my $row = <$str_h>) {
chomp $row;
print join(',',
map { length $_ ? $_ : 'N/A'} split /,/, $row, -1
), "\n";
}
Output:
E:\Home> t.pl 2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,N/A,Clear 2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,N/A,N/A,N/A,N/A
Você também pode usar:
pos $str -= 1 while $str =~ s{,(,|\n)}{,N/A$1}g;
Explicação: Quando s///
encontra um ,,
e substitui-lo com ,N/A,
já se mudou para o personagem após a última vírgula. Então, ele vai perder algumas vírgulas consecutivas, se você usar apenas
$str =~ s{,(,|\n)}{,N/A$1}g;
Por isso, eu usei um loop para mover pos $str
volta por um personagem depois de cada substituição bem sucedido.
Agora, como @ ysth mostra :
$str =~ s!,(?=[,\n])!,N/A!g;
faria o while
desnecessário.
Você pode procurar
(?<=,)(?=,|$)
e substituir com que N / A.
Esta expressão regular corresponde ao espaço (vazio) entre duas ou vírgulas entre uma vírgula e de fim de linha.
A versão rápida e suja corte:
my $rawData = "2008-02-06,8:00 AM,14.0,6.0,59,1027,-9999.0,West,6.9,-,N/A,,Clear
2008-02-06,9:00 AM,16,6,40,1028,12,WNW,10.4,,,,\n";
while ($rawData =~ s/,,/,N\/A,/g) {};
print $rawData;
Não é o código mais rápido, mas o mais curto. Ele deve percorrer no máximo duas vezes.
Não é um regex, mas não demasiado complicado ou:
$string = join ",", map{$_ eq "" ? "N/A" : $_} split (/,/, $string,-1);
O ,-1
é necessário no final a força split
para incluir quaisquer campos vazios na parte final da cadeia.