¿Cómo uso Perl para intercalar caracteres entre coincidencias consecutivas con una sustitución de expresiones regulares?
-
22-07-2019 - |
Pregunta
Las siguientes líneas de valores separados por comas contienen varios campos vacíos 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"
Quiero reemplazar estos campos vacíos con valores 'N / A', por lo que decidí hacerlo a través de una sustitución de expresiones regulares.
Intenté esto primero:
$rawdata =~ s/,([,\n])/,N\/A/g; # RELABEL UNAVAILABLE DATA AS 'N/A'
que devolvió
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
No es lo que quería. El problema ocurre cuando ocurren más de dos comas consecutivas. La expresión regular engulle dos comas a la vez, por lo que comienza en la tercera coma en lugar de la segunda cuando vuelve a escanear la cadena.
Pensé que esto podría tener algo que ver con las afirmaciones de búsqueda anticipada vs.
$rawdata =~ s/(?<=,)([,\n])|,([,\n])$/,N\/A$1/g; # RELABEL UNAVAILABLE DATA AS 'N/A'
que resultó en:
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
Eso tampoco funcionó. Simplemente cambió los pares de comas por uno.
Sé que lavar esta cadena a través de la misma expresión regular dos veces lo hará, pero eso parece crudo. Seguramente, debe haber una manera de obtener una sola sustitución de expresiones regulares para hacer el trabajo. ¿Alguna sugerencia?
La cadena final debería verse así:
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
Solución
No pude entender lo que intentabas hacer en tu ejemplo de mirar atrás, pero sospecho que estás sufriendo un error de precedencia allí, y que todo después de la mirada atrás debería estar encerrado en un |
no evite mirar hacia atrás.
Comenzando desde cero, lo que intenta hacer suena bastante simple: coloque N / A después de una coma si le sigue otra coma o una nueva línea:
s!,(?=[,\n])!,N/A!g;
Ejemplo:
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);
Salida:
"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"
Otros consejos
EDITAR: tenga en cuenta que puede abrir un identificador de archivo en la cadena de datos y dejar que readline
se ocupe de las terminaciones de línea:
#!/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 EDITAR: tenga en cuenta que puede abrir un identificador de archivo en la cadena de datos y dejar que readline
se ocupe de las terminaciones de línea:
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
Salida:
pos $str -= 1 while $str =~ s{,(,|\n)}{,N/A$1}g;
También puedes usar:
$str =~ s{,(,|\n)}{,N/A$1}g;
Explicación: cuando s ///
encuentra un ,,
y lo reemplaza con , N / A,
ya se ha movido al personaje después de la última coma. Por lo tanto, se perderán algunas comas consecutivas si solo usa
$str =~ s!,(?=[,\n])!,N/A!g;
Por lo tanto, usé un bucle para mover pos $ str
hacia atrás por un personaje después de cada sustitución exitosa.
Ahora, como @ ysth muestra :
<*>
haría que el while
fuera innecesario.
? EDITAR: tenga en cuenta que puede abrir un identificador de archivo en la cadena de datos y dejar que readline
se ocupe de las terminaciones de línea:
<*>
Salida:
<*>
También puedes usar:
<*>
Explicación: cuando s ///
encuentra un ,,
y lo reemplaza con , N / A,
ya se ha movido al personaje después de la última coma. Por lo tanto, se perderán algunas comas consecutivas si solo usa
<*>
Por lo tanto, usé un bucle para mover pos $ str
hacia atrás por un personaje después de cada sustitución exitosa.
Ahora, como @ ysth muestra :
<*>
haría que el while
fuera innecesario.
: 'N/A'} split /,/, $row, -1
), "\n";
}
Salida:
<*>También puedes usar:
<*> Explicación: cuando s ///
encuentra un ,,
y lo reemplaza con , N / A,
ya se ha movido al personaje después de la última coma. Por lo tanto, se perderán algunas comas consecutivas si solo usa
Por lo tanto, usé un bucle para mover pos $ str
hacia atrás por un personaje después de cada sustitución exitosa.
Ahora, como @ ysth muestra :
<*> haría que el while
fuera innecesario.
Puedes buscar
(?<=,)(?=,|$)
y reemplácelo con N / A.
Esta expresión regular coincide con el espacio (vacío) entre dos comas o entre una coma y el final de la línea.
La versión rápida y sucia del hack:
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;
No es el código más rápido, sino el más corto. Debería pasar al máximo dos veces.
No es una expresión regular, pero tampoco es demasiado complicada:
$string = join ",", map{ No es una expresión regular, pero tampoco es demasiado complicada:
<*>
El , -1
se necesita al final para obligar a split
a incluir cualquier campo vacío al final de la cadena.
eq "" ? "N/A" : No es una expresión regular, pero tampoco es demasiado complicada:
<*>
El , -1
se necesita al final para obligar a split
a incluir cualquier campo vacío al final de la cadena.
} split (/,/, $string,-1);
El , -1
se necesita al final para obligar a split
a incluir cualquier campo vacío al final de la cadena.