¿Cómo uso Perl para intercalar caracteres entre coincidencias consecutivas con una sustitución de expresiones regulares?

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

  •  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
¿Fue útil?

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 (?: ...) para que | 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top