Чи існує ярлик Perl для підрахунку кількості збігів у рядку?


78

Припустимо, у мене є:

my $string = "one.two.three.four";

Як я повинен грати з контекстом, щоб дізнатися, скільки разів шаблон знайшов збіг (3)? Чи можна це зробити за допомогою однокласника?

Я спробував це:

my ($number) = scalar($string=~/\./gi);

Я думав, що, розмістивши дужки навколо $number, я змушую контекст масиву, а за допомогою scalar, я отримаю рахунок. Проте все, що я отримую - це 1.

Відповіді:


119

Це ставить сам регулярний вираз у скалярний контекст, а це не те, що ви хочете. Натомість вставте регулярний вираз у контекст списку (щоб отримати кількість збігів) і помістіть його у скалярний контекст.

 my $number = () = $string =~ /\./gi;

4
Ну, perlsecret пропонує "Сатурн" як альтернативну назву. :)
oalders

1
Хтось може пояснити мені цю частинку коду? Я новачок у perl, і мені все ще не дуже зручно в контексті.
Едвард Гарган,

Перша частина, () = $string =~ /\./giзмусить оператор збігу повернути результати збігу в контексті списку. Це схоже на my @results = $string =~ /\./gi;. Далі, my $numberчастина є скалярним значенням. Присвоєння результатів контексту списку скаляру повертає його довжину. Це те саме my $count = @some_list, що повертає довжину масиву. Моя відповідь нижче - ще один спосіб візуалізації поведінки тут.
Роберт П.

35

Я думаю, що найбільш чітким способом описати це було б, щоб уникнути миттєвого відбору до скаляра. Спочатку призначте масив, а потім використовуйте цей масив у скалярному контексті. В основному це = () =буде робити ідіома, але без ідіоми (що рідко використовується):

my $string = "one.two.three.four";
my @count = $string =~ /\./g;
print scalar @count;

15
+1 за найпростіший спосіб, оператор козла - це страшно.
Маттео Ріва

2
Дужки навколо @countне потрібні.
Маттео Ріва

22

Також див. Perlfaq4 :

Існує ряд способів із різною ефективністю. Якщо вам потрібен підрахунок певного одиничного символу (X) у рядку, ви можете використовувати функцію tr /// приблизно так:

$string = "ThisXlineXhasXsomeXx'sXinXit";
$count = ($string =~ tr/X//);
print "There are $count X characters in the string";

Це чудово, якщо ви просто шукаєте одного персонажа. Однак якщо ви намагаєтеся порахувати кілька символьних підрядків у більшому рядку, tr /// не працюватиме. Що ви можете зробити, це обернути цикл while () навколо глобального збігу шаблонів. Наприклад, давайте порахуємо цілі від’ємні числа:

$string = "-9 55 48 -2 23 -76 4 14 -44";
while ($string =~ /-\d+/g) { $count++ }
print "There are $count negative numbers in the string";

Інша версія використовує глобальний збіг у контексті списку, а потім призначає результат скаляру, виробляючи підрахунок кількості збігів.

$count = () = $string =~ /-\d+/g;


6

Спробуйте це:


my $string = "one.two.three.four";
my ($number) = scalar( @{[ $string=~/\./gi ]} );

Це повертається 3для мене. Створюючи посилання на масив, регулярний вираз обчислюється в контексті списку, а @{..}посилання на масив відміняє посилання.


4
Вам не потрібна жодна з цих дужок.
Бред Гілберт,

1
Треба сказати, що цей метод мені подобається більше, ніж козячий. Насправді мені майже все подобається краще, ніж козлятина.
Wick

0

Я помітив, що якщо у вашому регулярному виразі є умова АБО (наприклад, /(K..K)|(V.AK)/gi ) то у створеному масиві можуть бути невизначені елементи, які входять у підрахунок наприкінці.

Наприклад:

my $seq = "TSYCSKSNKRCRRKYGDDDDWWRSQYTTYCSCYTGKSGKTKGGDSCDAYYEAYGKSGKTKGGRNNR";
my $regex = '(K..K)|(V.AK)';
my $count = () = $seq =~ /$regex/gi;
print "$count\n";

Дає значення рахунку 6.

Я знайшов рішення в цій публікації Як видалити всі undefs з масиву?

my $seq = "TSYCSKSNKRCRRKYGDDDDWWRSQYTTYCSCYTGKSGKTKGGDSCDAYYEAYGKSGKTKGGRNNR";
my $regex = '(K..K)|(V.AK)';
my @count = $seq =~ /$regex/gi;
@count = grep defined, @count; 
my $count = scalar @count;
print "$count\n";

Що потім дає правильну відповідь із трьох.


-1

Інший спосіб,

my $string = "one.two.three.four";
@s = split /\./,$string;
print scalar @s - 1;


-1

Метод Friedo полягає в наступному : $a = () = $b =~ $c.

Але це можна спростити ще більше, просто ($a) = $b =~ $cтак:

my ($matchcount) = $text =~ s/$findregex/ /gi;

Ви можете подякувати, просто оберніть це у функцію getMatchCount(), і не турбуйтеся про те, що це знищить переданий рядок.

З іншого боку, ви можете додати обмін, що може бути трохи більше обчислень, але не призведе до зміни рядка.

my ($matchcount) = $text =~ s/($findregex)/$1/gi;

За винятком того, що це заміна, а не збіг: вона знищить початковий рядок. І це така сама ідея, як у @Mike 6 років тому.
fishinear

@fishinear: Це сильно відрізняється від Майка. Він міг надрукувати його, але не зберігати в змінній. Різниця значна.
HoldOffHunger

1
Якщо вам потрібно не руйнувати, просто s / (регулярний вираз) / $ 1 / g або / (= регулярний вираз) // g, якщо вам подобається жити небезпечно.
android.weasel

@ android.weasel Ой, привіт, хороша думка! Оновлення з цим зауваженням. Я зазвичай обертаю подібні речі у функції, тому мені самому не доводиться турбуватися про руйнування переданих аргументів (не впевнений, що це швидше, оскільки зараз він робить обмін). Але це корисна інформація, додавши!
HoldOffHunger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.