Как использовать Perl для перемежения символов между последовательными совпадениями с заменой регулярных выражений?

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

  •  22-07-2019
  •  | 
  •  

Вопрос

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

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

Я хочу заменить эти пустые поля значениями «Н/Д», поэтому я решил сделать это с помощью замены регулярных выражений.

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

$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
Это было полезно?

Решение

Я не мог до конца понять, что вы пытались сделать в своем примере с просмотром назад, но подозреваю, что вы страдаете от ошибки приоритета и что все, что происходит после просмотра назад, должно быть заключено в (?: ... ) Итак | не избегает просмотра назад.

Начиная с нуля, то, что вы пытаетесь сделать, звучит довольно просто:поместите 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"

Другие советы

РЕДАКТИРОВАТЬ:Обратите внимание, что вы можете открыть дескриптор файла для строки данных и позволить 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 ненужный.

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

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

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

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

Быстрая и грязная версия хака:

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;

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

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

$string = join ",", map{

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

<*>

, - 1 требуется в конце, чтобы split включал любые пустые поля в конце строки.

eq "" ? "N/A" :

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

<*>

, - 1 требуется в конце, чтобы split включал любые пустые поля в конце строки.

} split (/,/, $string,-1);

, - 1 требуется в конце, чтобы split включал любые пустые поля в конце строки.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top