опубликован: 2024-08-04 22:57
последняя редакция: 2024-08-07 13:51
Красивая эмуляция конструкции вида switch-case
Вначале напомню, что в Перле конструкция вида if - else - endif может быть заменена сокращённой записью. Например, выражение:if ($x >= 0) {
print 'positive';
}
else {
print 'negative';
}
плюс-минус эквивалентно записи:
print $x >= 0 ? 'postitve' : 'negative';
Перловики сплошь и рядом вставляют подобные конструкции в свои программы, поскольку очень удобно для коротких выражений и не хочется развешивать "лапшу".
С другой стороны, в перле отсутствуют штатный способ создать конструкцию наподобие switch - case. Можно заменить кончено на if - elsif - elsif - ..., но получается немного коряво. По крайней мере, мне кажется что коряво.
И вот, на днях, просматривая какой-то западный ролик на ютубе с одной перловой конференции, заметил использование классной конструкции для switch - case, с использованием упомянутого условного тернарного оператора. Уже поделился находкой где только можно, приведу и здесь. Наслаждайтесь:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $x = 2;
say
!$x ? 'zero'
: $x == 1 ? 'one'
: $x == 2 ? 'two'
: $x == 3 ? 'three'
: 'too many'
;
Конечно, в конечном счёте всё элементарно, но почему-то не приходило в голову. Видимо мы зашорены раз и навсегда усвоенными шаблонами..
Продолжение истории
В телеграм-канале Modern::Perl прошло плодотворное обсуждение различных вариантов эмуляции switch-case в Перле. Помимо приведённой в этой заметке конструкции, было предложено ещё несколько вариантов, а заодно составлен тестовый бенчмарк для каждого из этих вариантов. Листинг теста и результаты на моём рабочем компе привожу ниже.
#!/usr/bin/perl
use strict;
use warnings;
use v5.10;
use feature 'say';
use Benchmark qw(:all);
my $sparam = 1;
cmpthese(100_000, +{
"?:" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
say
!$x ? 'zero'
: $x == 1 ? 'one'
: $x == 2 ? 'two'
: $x == 3 ? 'three'
: 'too many'
;
},
"&& ||" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
say !$x && 'zero'
|| $x == 1 && 'one'
|| $x == 2 && 'two'
|| $x == 3 && 'three'
|| 'too many'
;
},
"and or" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
say(!$x and 'zero'
or $x == 1 and 'one'
or $x == 2 and 'two'
or $x == 3 and 'three'
or 'too many'
);
},
"elsif" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
say do {
if ( !$x ) { 'zero' }
elsif($x == 1) { 'one' }
elsif($x == 2) { 'two' }
elsif($x == 3) { 'three' }
else { 'too many' }
};
},
"sub return if ..." => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
say((sub {
return 'zero' if !$x;
return 'one' if 1 == $x;
return 'two' if 2 == $x;
return 'three' if 3 == $x;
return 'too many';
})->());
},
"given" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
use feature qw/switch/;
#no warnings qw/deprecated::smartmatch/;
say do {
given($x) {
'zero' when !$_;
'one' when 1;
'two' when 2;
'three' when 3;
default { 'too many' }
}
};
},
"switch" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
use Switch '__';
say((sub {
switch($x) {
case !__ { return 'zero' }
case 1 { return 'one' }
case 2 { return 'two' }
case 3 { return 'three' }
else { return 'too many' }
}
})->());
},
"{}" => sub {
state $init;
$init//=srand($sparam);
my $x = int rand 5;
# Тут выражения вычисляются все, а не последовательно,
# поэтому раскрывают частный случай
say +{
0 => 'zero',
1 => 'one',
2 => 'two',
3 => 'three',
}->{$x} // 'too many';
},
});
Ну, и мои результаты, если интересно
(warning: too few iterations for a reliable count)
Rate switch sub return if ... {} and or elsif given && || ?:
switch 54645/s -- -78% -81% -82% -83% -86% -87% -88%
sub return if ... 250000/s 357% -- -12% -17% -20% -37% -42% -45%
{} 285714/s 423% 14% -- -6% -9% -29% -34% -37%
and or 303030/s 455% 21% 6% -- -3% -24% -30% -33%
elsif 312500/s 472% 25% 9% 3% -- -22% -28% -31%
given 400000/s 632% 60% 40% 32% 28% -- -8% -12%
&& || 434783/s 696% 74% 52% 43% 39% 9% -- -4%
?: 454545/s 732% 82% 59% 50% 45% 14% 5% --