Comment utiliser Perl pour intercaler des caractères entre des correspondances consécutives avec une substitution regex?
-
22-07-2019 - |
Question
Les lignes suivantes de valeurs séparées par des virgules contiennent plusieurs champs vides consécutifs:
$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 souhaite remplacer ces champs vides par des valeurs 'N / A', c'est pourquoi j'ai décidé de le faire via une substitution de regex.
J'ai d'abord essayé ceci:
$rawdata =~ s/,([,\n])/,N\/A/g; # RELABEL UNAVAILABLE DATA AS 'N/A'
qui est retourné
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. La regex engloutit deux virgules à la fois, elle commence donc par la troisième virgule plutôt que par la seconde quand elle réanalyse la chaîne.
Je pensais que cela pouvait avoir quelque chose à voir avec les assertions d'anticipation vs d'anticipation, alors j'ai essayé la regex suivante:
$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. Cela a juste décalé les appariements d'une virgule.
Je sais que laver cette chaîne deux fois dans la même expression rationnelle le fera, mais cela semble grossier. Il doit sûrement y avoir un moyen d’obtenir une seule substitution de regex pour faire le travail. Des suggestions?
La dernière chaîne 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
La solution
Je ne pouvais pas vraiment comprendre ce que vous essayiez de faire dans votre exemple de lookbehind, mais je suppose que vous souffrez d'une erreur de priorité là-bas et que tout ce qui suit le lookbehind doit être placé dans un (?: ...)
afin que le |
n'évite pas de faire le lookbehind.
Partir de zéro, ce que vous essayez de faire semble assez simple: placez N / A après une virgule si elle est suivie par une autre virgule ou une nouvelle 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"
Autres conseils
EDIT: notez que vous pouvez ouvrir un descripteur de fichier dans la chaîne de données et laisser readline
gérer 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 EDIT: notez que vous pouvez ouvrir un descripteur de fichier dans la chaîne de données et laisser readline
gérer les fins de ligne:
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
Sortie:
pos $str -= 1 while $str =~ s{,(,|\n)}{,N/A$1}g;
Vous pouvez également utiliser:
$str =~ s{,(,|\n)}{,N/A$1}g;
Explication: Lorsque s ///
trouve un ,,
et le remplace par , N / A,
, il est déjà passé à la caractère après la dernière virgule. Donc, il manquera des virgules consécutives si vous utilisez uniquement
$str =~ s!,(?=[,\n])!,N/A!g;
J'ai donc utilisé une boucle pour déplacer pos $ str
d'un caractère après chaque substitution réussie.
Maintenant, comme @ émissions de musique :
<*>
rendrait le tant que
inutile.
? EDIT: notez que vous pouvez ouvrir un descripteur de fichier dans la chaîne de données et laisser readline
gérer les fins de ligne:
<*>
Sortie:
<*>
Vous pouvez également utiliser:
<*>
Explication: Lorsque s ///
trouve un ,,
et le remplace par , N / A,
, il est déjà passé à la caractère après la dernière virgule. Donc, il manquera des virgules consécutives si vous utilisez uniquement
<*>
J'ai donc utilisé une boucle pour déplacer pos $ str
d'un caractère après chaque substitution réussie.
Maintenant, comme @ émissions de musique :
<*>
rendrait le tant que
inutile.
: 'N/A'} split /,/, $row, -1
), "\n";
}
Sortie:
<*>Vous pouvez également utiliser:
<*> Explication: Lorsque s ///
trouve un ,,
et le remplace par , N / A,
, il est déjà passé à la caractère après la dernière virgule. Donc, il manquera des virgules consécutives si vous utilisez uniquement
J'ai donc utilisé une boucle pour déplacer pos $ str
d'un caractère après chaque substitution réussie.
Maintenant, comme @ émissions de musique :
<*> rendrait le tant que
inutile.
Vous pouvez rechercher
(?<=,)(?=,|$)
et remplacez-le par N / A.
Cette expression rationnelle correspond à l'espace (vide) entre deux virgules ou entre une virgule et une fin de ligne.
La version rapide et sale du 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;
Pas le code le plus rapide, mais le plus court. Il devrait boucler au maximum deux fois.
Pas une regex, mais pas trop compliquée non plus:
$string = join ",", map{ Pas une regex, mais pas trop compliquée non plus:
<*>
Le , - 1
est nécessaire à la fin pour forcer split
à inclure les champs vides à la fin de la chaîne.
eq "" ? "N/A" : Pas une regex, mais pas trop compliquée non plus:
<*>
Le , - 1
est nécessaire à la fin pour forcer split
à inclure les champs vides à la fin de la chaîne.
} split (/,/, $string,-1);
Le , - 1
est nécessaire à la fin pour forcer split
à inclure les champs vides à la fin de la chaîne.