Как использовать Perl для перемежения символов между последовательными совпадениями с подстановкой регулярных выражений? - PullRequest
3 голосов
/ 29 октября 2009

Следующие строки значений через запятую содержат несколько последовательных пустых полей:

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

Я хочу заменить эти пустые поля значениями 'N / A', поэтому я решил сделать это с помощью подстановки регулярных выражений.

Я пробовал это в первую очередь:

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

вернул

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

Не то, что я хотел. Проблема возникает, когда возникает более двух последовательных запятых. Регулярное выражение поглощает две запятые за раз, поэтому оно начинает с третьей запятой, а не со второй, когда проверяет строку.

Я подумал, что это может быть как-то связано с утверждениями «взгляд вперед» и «просмотр назад», поэтому я попробовал следующее выражение:

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

, что привело к:

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

Это тоже не сработало. Он просто сдвинул запятую на единицу.

Я знаю, что промывание этой строки дважды одним и тем же регулярным выражением сделает это, но это кажется грубым. Конечно, должен быть способ получить одну замену регулярного выражения для выполнения этой работы. Есть предложения?

Последняя строка должна выглядеть следующим образом:

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

Ответы [ 5 ]

3 голосов
/ 29 октября 2009

EDIT: обратите внимание, что вы можете открыть дескриптор файла для строки данных и позволить readline иметь дело с окончаниями строк:

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

Выход:

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

Вы также можете использовать:

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

Объяснение: Когда s/// находит ,, и заменяет его на ,N/A,, оно уже перемещается к персонажу после последней запятой. Таким образом, он пропустит несколько последовательных запятых, если вы используете только

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

Поэтому я использовал цикл для перемещения pos $str назад на символ после каждой успешной замены.

Теперь, как показывает @ ysth :

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

сделает while ненужным.

2 голосов
/ 29 октября 2009

Вы можете искать

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

и замените его на N / A.

Это регулярное выражение соответствует (пустому) пробелу между двумя запятыми или между запятой и концом строки.

2 голосов
/ 29 октября 2009

Я не мог разобрать, что вы пытались сделать в своем примере с поиском, но я подозреваю, что вы страдаете от ошибки предшествования, и что все, что после просмотра, должно быть заключено в (?: ... ), поэтому 1002 * не избегает смотреть назад.

Начиная с нуля, то, что вы пытаетесь сделать, звучит довольно просто: поставьте N / A после запятой, если за ней следует другая запятая или символ новой строки:

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

Пример:

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

Выход:

"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"
1 голос
/ 29 октября 2009

Не регулярное выражение, но и не слишком сложное:

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

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * в конце необходим для принудительного split включения любых пустых полей в конце строки.

1 голос
/ 29 октября 2009

Быстрая и грязная версия взлома:

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;

Не самый быстрый код, но самый короткий. Он должен пройти через максимум два раза.

...