Perl регулярное выражение, которое захватывает ВСЕ двухбуквенные вхождения в строке - PullRequest
2 голосов
/ 04 января 2011

Все еще стараюсь учить себя Perl. Я пытаюсь написать код, который будет считать строки файла, которые содержат двойные буквы, а затем помещать скобки вокруг этих двойных букв.

Теперь, что я придумаю, найдет первое вхождение двойных букв, но не каких-либо других. Например, если строка:

Ампер, Джеймс Уотт, Боб Трансформер и т. Д. Эти пионеры провели много

Мой код будет отображать это:

19 Ампер, Джеймс Ва (tt), Боб Трансформер и т. Д. Эти пионеры провели много

«19» - это количество (строк, содержащих двойные буквы), и оно получает «tt» от «Watt», но пропускает «ee» в «pioneers».

Ниже мой код:

$file = '/path/to/file/electricity.txt';        
open(FH, $file) || die "Cannot open the file\n";        

my $counter=0;

while (<FH>) {
    chomp();
    if (/(\w)\1/) {
        $counter += 1;
        s/$&/\($&\)/g;
        print "\n\n$counter $_\n\n";
    } else {
        print "$_\n";
    }
}

close(FH);          

Что я пропускаю?

Ответы [ 4 ]

4 голосов
/ 04 января 2011
use strict;
use warnings;
use 5.010;
use autodie;

my $file = '/path/to/file/electricity.txt';        
open my $fh, '<', $file;        

my $counter = 0;

while (<$fh>) {
    chomp;
    if (/(\w)\1/) {
        $counter++;
        s/
          (?<full>
               (?<letter>\p{L})
               \g{letter}
          )
        /($+{full})/xg;
        $_ = $counter . ' ' . $_;
    }
    say;
}

Вы пропускаете несколько вещей. строгие и предупреждения ;5,010 (или выше!) Для скажем ; autodie , чтобы вам не приходилось набирать их "или умри"; Лексические файловые дескрипторы и форма с тремя аргументами open ;Немного придирчив, но знает, когда (не) использовать парены для вызовов функций ;Понимание того, почему вы не должны использовать $ & ;Оператор автоинкремента ..

Но в части регулярного выражения $ & устанавливается только на совпадения (m //), а не на подстановку На самом деле нет, ysthкак обычно.Извините!

(я позволил себе немного изменить ваше регулярное выражение; в нем используются именованные захваты - (?) Вместо открытых паренов, доступ к которым осуществляется через нотацию \ g {} внутри регулярного выражения и % + хэш вне его - и свойства в стиле Unicode - \ p {Etc}).Намного больше о них в perlre и perluniprops соответственно.

3 голосов
/ 04 января 2011

Вам необходимо использовать обратную ссылку:

#! /usr/bin/env perl

use warnings;
use strict;

my $line = "this is a doubble letter test of my scrippt";

$line =~ s/([[:alpha:]])(\1)/($1$2)/g;

print "$line\n";

А теперь тест.

$ ./test.pl
this is a dou(bb)le le(tt)er test of my scri(pp)t

Работает!

Когда вы делаете замену, вы используете $1 для представления того, что в скобках. Когда вы ссылаетесь на часть самого регулярного выражения, вы используете форму \1.

[[:alpha:]] - это специальный класс POSIX. Вы можете узнать больше информации, набрав

$ perldoc perlre

в командной строке.

2 голосов
/ 04 января 2011

Вы слишком усложняете вещи, балуясь $&. s///g возвращает количество замен, выполненных при использовании в скалярном контексте, так что вы можете сделать все это за один снимок без необходимости подсчитывать совпадения вручную или отслеживать позицию каждого совпадения:

#!/usr/bin/env perl

use strict;
use warnings;

my $text = 'James Watt, a pioneer of wattage engineering';

my $doubles = $text =~ s/(\w)\1/($1$1)/g;

print "$doubles $text\n";

Выход:

4 James Wa(tt), a pion(ee)r of wa(tt)age engin(ee)ring

Edit: OP заявил в комментариях, что рассматриваемое упражнение говорит, что не следует использовать =~, так что вот решение, не основанное на регулярном выражении, так как все совпадения с регулярным выражением используют =~ (неявно или явно ):

#!/usr/bin/env perl

use strict;
use warnings;

my $text = 'James Watt, a pioneer of wattage engineering';

my $doubles = 0;
for my $i (reverse 1 .. length $text) {
    if (substr($text, $i, 1) eq substr($text, $i - 1, 1)) {
        $doubles++;
        substr($text, $i - 1, 2) = '(' . substr($text, $i - 1, 2) . ')';
    }
}

print "$doubles $text\n";
1 голос
/ 04 января 2011

Проблема в том, что вы используете $ & во втором регулярном выражении, которое соответствует только первому вхождению набора из двух букв

 if (/(\w)\1/) { #first occurance matched, so the pattern in the replace regex will only be that particular set of double letters

Попробуйте сделать что-то вроде этого: s/(\w)\1/\($1$1\)/g; вместо s/$&/\($&\)/g; Полный код после редактирования:

$file = '/path/to/file/electricity.txt';        
open(FH, $file) || die "Cannot open the file\n";        

my $counter=0;

while (<FH>) {
    chomp();
    if (s/(\w)\1/\($1$1\)/g) {
        $counter++;
        print "\n\n$counter $_\n\n";
    } else {
        print "$_\n";
    }
}

close(FH);   

обратите внимание, что вы можете использовать s /// g replace в условном выражении, которое имеет значение true, когда произошла замена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...