2009-10-29 3 views
3

Les lignes de valeurs séparées par des virgules suivantes contient plusieurs champs vides consécutifs:Comment utiliser Perl pour intercaler des caractères entre des correspondances consécutives avec une substitution d'expressions régulières?

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

Je veux remplacer ces champs vides avec des valeurs « N/A », qui est la raison pour laquelle j'ai décidé de le faire via une substitution regex .

J'ai essayé tout d'abord:

$rawdata =~ s/,([,\n])/,N\/A/g; # RELABEL UNAVAILABLE DATA AS 'N/A' 

qui sont retournés

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 

pas ce que je voulais. Le problème se produit lorsque plus de deux virgules consécutives se produisent. L'expression rationnelle englobe deux virgules à la fois, de sorte qu'elle commence à la troisième virgule plutôt qu'à la seconde lorsqu'elle redessine la chaîne.

Je pensais que cela pourrait être quelque chose à voir avec les affirmations lookback par rapport préanalyse, alors j'ai essayé la regex suivante sur:

$rawdata =~ s/(?<=,)([,\n])|,([,\n])$/,N\/A$1/g; # RELABEL UNAVAILABLE DATA AS 'N/A' 

qui a abouti à:

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 

Cela n'a pas fonctionné non plus. Il a juste décalé les paires de virgules par un.

Je sais que le lavage de cette chaîne par le même regex deux fois le fera, mais cela semble brut. Sûrement, il doit y avoir un moyen d'obtenir une seule substitution de regex pour faire le travail. Aucune suggestion?

La chaîne finale devrait ressembler à ceci:

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 
+1

La réponse de @Zaid @ ysth fait exactement ce que vous voulez. –

Répondre

2

Je ne pouvais pas faire tout ce que vous essayez de faire dans votre exemple lookbehind, mais je soupçonne que vous souffrez d'une erreur de priorité là-bas, et que tout après la lookbehind doit être enfermé dans un (?: ...) donc les | doesn ne pas éviter le lookbehind.

A partir de zéro, ce que vous essayez de faire des sons assez simple: place N/A après une virgule si elle est suivie par une autre virgule ou un saut de ligne:

s!,(?=[,\n])!,N/A!g; 

Exemple:

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); 

sortie:

"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" 
+0

@ysth: D'accord. Cela fonctionne définitivement. Est-ce parce que l'assertion lookahead est non-capturante? – Zaid

+0

+1 Je ne sais pas pourquoi mais j'évite les assertions. Dans ce cas, je ne pouvais pas voir l'évidence à cause de mon aversion. –

+0

Drôle comme ces solutions regex ont tendance à être .... – Zaid

3

EDIT: Notez que vous pouvez ouvrir un descripteur de fichier à la chaîne de données et de laisser readline accord avec les fins de ligne:

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

Sortie:

 
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 

Vous pouvez également utiliser:

pos $str -= 1 while $str =~ s{,(,|\n)}{,N/A$1}g; 

Explication: Lorsque s/// trouve un ,, et le remplace par ,N/A, il a déjà déplacé le caractère après la virgule. Ainsi, il manquera quelques virgules consécutives si vous utilisez uniquement

$str =~ s{,(,|\n)}{,N/A$1}g; 

Par conséquent, j'ai utilisé une boucle pour déplacer pos $str arrière par un caractère après chaque substitution réussie.

Maintenant, comme @ysth shows:

$str =~ s!,(?=[,\n])!,N/A!g; 

rendrait la while inutile.

+1

Nice. Bon exemple que si les expressions régulières sont fréquemment utilisées dans Perl, elles ne sont pas toujours la meilleure solution. – jamessan

+0

@Sinan: Je préfère ne pas gérer les handles de fichiers. Les données sont déjà chargées dans une chaîne avec '\ n's. Est ce que je veux possible avec un regex 's ///'? – Zaid

+0

@Sinan: Évidemment, j'ai beaucoup à apprendre sur Perl. C'est un merveilleux one-liner, qui fait exactement ce dont j'ai besoin. Absolument magnifique. – Zaid

1

La version hack rapide et sale:

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; 

pas le code le plus rapide, mais le plus court. Il devrait traverser au maximum deux fois.

+0

Concis, mais comme vous l'avez dit, rapide et sale. – Zaid

2

Vous pouvez rechercher

(?<=,)(?=,|$) 

et de remplacer cela par N/A.

Cette expression régulière correspond à l'espace (vide) entre deux virgules ou entre une virgule et une fin de ligne.

+0

+1 mais il faudrait que s! (? <=,) (? =, | \ N)! N/A! G; 'pour attraper un champ vide à la fin d'une ligne. –

+0

Ouais, je venais juste de le remarquer aussi. –

1

Pas regex, mais pas trop compliqué, soit:

$string = join ",", map{$_ eq "" ? "N/A" : $_} split (/,/, $string,-1); 

Le ,-1 est nécessaire à la fin pour forcer split d'inclure des champs vides à la fin de la chaîne.

+0

Cela échouerait pour un champ vide à la fin de la ligne car il contiendrait '" \ n "' c'est pourquoi je 'chomp' d'abord dans mon exemple' split'. –

+0

@SU - bonne prise. Il est préférable d'utiliser ceci sur une entrée chomped. – mob

Questions connexes