2010-10-21 3 views
25

«Оператор goatse» или idiom =()= в Perl вызывает выражение, которое будет оцениваться в контексте списка.Является ли Perl Goatse 'Secret Operator' эффективным?

Пример может служить:

my $str = "5 and 4 and a 3 and 2 1 BLAST OFF!!!"; 
my $count =()= $str =~ /\d/g; # 5 matches... 
print "There are $count numbers in your countdown...\n\n"; 

Как я интерпретирую использовать, это то, что происходит:

  1. $str =~ /\d/g соответствует всем цифрам. Переключатель g и список перечисляют список этих совпадений. Пусть это будет пример «List Producer», и в Perl это может быть много.
  2. =()= вызывает присвоение пустующему списку, поэтому все фактические совпадения копируются в пустой список.
  3. Отнесение в скалярном контексте в $ подсчета списка, полученного в пункте 2. дает отсчет списка или результат 5.
  4. счетчик ссылок пустого списка =()= стремится к нулю после скалярного задания. Затем копия элементов списка удаляется Perl.

Вопросы по эффективности являются следующие:

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

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

+12

Это не так эффективно, как разработка пользовательского микрочипа для реализации вашей программы. – jrockway

+2

Почему голос? Разве это не справедливый вопрос о компромиссе между эффективностью и идиомой? – dawg

+16

Извините, я не мог устоять перед создателем тега для '[goatse]'. : Списки D – Ether

ответ

23

Perl 5 умеет копировать списки. Он копирует только столько элементов, сколько находится на левой стороне. Он работает, потому что назначение списка в скалярном контексте дает количество элементов в правой части. Таким образом, n элементов будут создаваться регулярным выражением, но они не будут скопированы и отброшены, просто отброшены. Вы можете увидеть разницу, которую добавляет дополнительная копия в наивном случае в сравнительном примере ниже.

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

naive: 10 
iterative: 10 
goatse: 10 

for 0 items: 
       Rate iterative goatse  naive 
iterative 4365983/s  --  -7%  -12% 
goatse 4711803/s  8%  --  -5% 
naive  4962920/s  14%  5%  -- 

for 1 items: 
       Rate  naive goatse iterative 
naive  749594/s  --  -32%  -69% 
goatse 1103081/s  47%  --  -55% 
iterative 2457599/s  228%  123%  -- 

for 10 items: 
       Rate  naive goatse iterative 
naive  85418/s  --  -33%  -82% 
goatse 127999/s  50%  --  -74% 
iterative 486652/s  470%  280%  -- 

for 100 items: 
      Rate  naive goatse iterative 
naive  9309/s  --  -31%  -83% 
goatse 13524/s  45%  --  -76% 
iterative 55854/s  500%  313%  -- 

for 1000 items: 
      Rate  naive goatse iterative 
naive  1018/s  --  -31%  -82% 
goatse 1478/s  45%  --  -75% 
iterative 5802/s  470%  293%  -- 

for 10000 items: 
      Rate  naive goatse iterative 
naive  101/s  --  -31%  -82% 
goatse 146/s  45%  --  -75% 
iterative 575/s  470%  293%  -- 

Вот код, который генерируется его:

#!/usr/bin/perl 

use strict; 
use warnings; 

use Benchmark; 

my $s = "a" x 10; 

my %subs = (
    naive => sub { 
     my @matches = $s =~ /a/g; 
     return scalar @matches; 
    }, 
    goatse => sub { 
     my $count =()= $s =~ /a/g; 
     return $count; 
    }, 
    iterative => sub { 
     my $count = 0; 
     $count++ while $s =~ /a/g; 
     return $count; 
    }, 
); 

for my $sub (keys %subs) { 
    print "$sub: @{[$subs{$sub}()]}\n"; 
} 

for my $n (0, 1, 10, 100, 1_000, 10_000) { 
    $s = "a" x $n; 
    print "\nfor $n items:\n"; 
    Benchmark::cmpthese -1, \%subs; 
} 
+1

+1: Спасибо. Я очень ценю, как вы подошли к логике этого, и вы захватили то, что я представляю себе так: чем больше у вас, тем лучше итерация. Но если Perl «умный» о копировании числа, которое необходимо с левой стороны, с '=() =' не все ли они? – dawg

+0

Нет, на стороне lefthand нет объектов, поэтому данные не копируются (но регулярное выражение все равно должно сгенерировать те, которые находятся справа). –

+0

Согласен, что если у вас есть что-то вроде '($ i, $ j, $ k) =/a/g;' будет копировать 3 элемента, даже если их 10. Но если у вас есть '() =/a/g;' Perl достаточно умный, чтобы увидеть, что нулевые присваивания копируются 0? – dawg

13

В вашем конкретном примере тест полезен:

my $str = "5 and 4 and a 3 and 2 1 BLAST OFF!!!"; 

use Benchmark 'cmpthese'; 

cmpthese -2 => { 
    goatse => sub { 
     my $count =()= $str =~ /\d/g; 
     $count == 5 or die 
    }, 
    while => sub { 
     my $count; 
     $count++ while $str =~ /\d/g; 
     $count == 5 or die 
    }, 
}; 

который возвращается:

  Rate goatse while 
goatse 285288/s  -- -57% 
while 661659/s 132%  -- 

В контексте списка $str =~ /\d/g выполняется сбор согласованной подстроки, даже если она не нужна. Пример while имеет регулярное выражение в скалярном (булевом) контексте, поэтому механизм регулярных выражений просто должен возвращать true или false, а не фактические совпадения.

И вообще, если у вас есть список функций производства и заботятся только о количестве предметов, написание короткого count функция быстрее:

sub make_list {map {$_**2} 0 .. 1000} 

sub count {scalar @_} 

use Benchmark 'cmpthese'; 

cmpthese -2 => { 
    goatse => sub {my $count =()= make_list; $count == 1001 or die}, 
    count => sub {my $count = count make_list; $count == 1001 or die}, 
}; 

, который дает:

  Rate goatse count 
goatse 3889/s  -- -26% 
count 5276/s 36%  -- 

Мои угадайте, почему sub быстрее, потому что вызовы подпрограмм оптимизированы для передачи списков без их копирования (передаются как псевдонимы).

+2

+1: Benchamarks всегда лучше, чем простое предположение. Благодаря! – dawg

3

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

Перед тем, как вы оцениваете, самый важный вопрос: «Это даже имеет значение?». Профиль перед тем, как вы оцениваете, и только беспокоитесь об этих вещах, когда у вас заканчиваются реальные проблемы. :)

Если вы ищете максимальную эффективность, Perl немного слишком высокий. :)

+1

«Это даже вопрос» - справедливый вопрос. Это имеет значение для меня * по двум причинам: 1) Мне любопытно! Если я использую 1 идиому против другой, мне нравится думать в затылке, почему я это делаю. 2) Если я использую ярлык, мне нравится понимать гайки и болты. Я так же легко привык печатать идиому '$ count ++, а $ s = ~/a/g', как я могу' $ count =() = $ s = ~/a/g; '. Если кто-то будет намного быстрее, чем другой, я буду склонен к его одобрению, не сказав, что другой «ошибается». – dawg

+0

Вы создаете теги wiki для этого «оператора»? http://stackoverflow.com/tags/goatse/info – Ether

+0

Я не занимаюсь этим. –