Comment utiliser Perl pour intercaler des caractères entre des correspondances consécutives avec une substitution regex?

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

  •  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
Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top