Поради щодо гольфу в Перлі?


47

Які загальні поради щодо гольфу в Перлі? Я шукаю ідеї, які можна застосувати до проблем із гольфом взагалі, які принаймні дещо характерні для Perl (наприклад, "видалити коментарі" - це не відповідь). Будь ласка, опублікуйте одну пораду за кожну відповідь.

Відповіді:


26

TMTOWTDI

Це найважливіша порада з гольфу Perl, яку ви повинні знати. Щоразу, коли ви дивитесь на якусь занадто довгу послідовність символів, яку ви абсолютно повинні мати, щоб виконати своє завдання, запитайте себе, чи немає іншого способу отримати той же ефект, використовуючи іншу функцію. Зазвичай є. Ось лише кілька:

  • ~~застосовує скалярний контекст і на 4 символи коротший, ніж scalar.

  • y///cна один знак коротший, ніж lengthпри отриманні довжини $_.

  • Потрібно перебрати символи $_? Замініть split//на /./gs. (Або використовувати, /./gякщо ви також хочете пропустити нові рядки.) Це працює з іншими змінними: замініть split//,$xна $x=~/./gs.

  • Кожен вбудований Perl щось повертає. printповертає 1, наприклад, для позначення успішного вводу / виводу. Якщо вам потрібно ініціалізувати $_до справжнього значення, наприклад, $_=print$fooдозволяє вбити двох птахів одним каменем.

  • Майже кожне твердження в Perl може бути записане як вираз, що дозволяє використовувати його в більш широкому спектрі контекстів. Кілька висловлювань можуть стати декількома виразами, пов'язаними ланцюжками разом з комами. Тести можна проводити з операторами короткого замикання ?: && ||, а також з andі or, які роблять те саме, але з перевагою нижче, ніж у всіх інших операторів (включаючи призначення). Петлі можна робити через mapабо grep. Навіть такі слова як next, lastі returnможе бути використано в контексті вираження, навіть якщо вони не повертаються! Пам'ятаючи про такі перетворення, ви надаєте можливості замінити кодові блоки на вирази, які можна вставити в більш широкий спектр контекстів.


$_=print""коротше, ніж $_=print$foo.
ASCIIThenANSI

5
Ідея полягає в тому, що вам вже потрібно друкувати $foo. В іншому випадку $_=1він набагато коротший $_=print""і має такий же ефект.
хлібниця

3
На третій ви маєте на увазі ітерацію над знаками в $x? Інакше можна було б просто зробити /./gsі /./g.
redbmk

25

Зловживайте спеціальними змінними Perl!

  • Як було відзначено в попередній відповіді $/і $"не започатковано за замовчуванням "\n"і " ", відповідно.

  • $,і $\вони встановлені undefза замовчуванням, і вони на 3 символи коротші.

  • Якщо встановлено $\значення, воно призведе до кожного print. Наприклад: perl -ple '$\="=".hex.$/'це зручний шістнадцятковий перетворювач.

  • Якщо ви не читаєте файли з командного рядка, ви можете використовувати -iперемикач командного рядка як додатковий канал для введення рядка. Його значення буде зберігатися в $^I.

  • $=примушує все, що йому призначено, бути цілим числом. Спробуйте запустити perl -ple '$_=$==$_'і давати йому різні прищепки. Так само $-змушує його значення бути невід'ємним цілим числом (тобто провідний тире трактується як нечисловий символ).

  • Ви можете використовувати $.як булевий прапор, який автоматично скидається на справжнє (ненульове) значення при кожній ітерації while(<>)циклу.


20

-n і неперевершені фігурні дужки

Добре відомо, що комутатор командного рядка -nможе використовуватися для виконання сценарію один раз для кожного рядка.

perl --help каже:

  -n                assume "while (<>) { ... }" loop around program

Що прямо не сказано, це те, що Perl не передбачає циклу навколо програми; він буквально обмотається while (<>) { ... }навколо нього.

Таким чином, наступні команди еквівалентні один одному:

 perl -e 'while(<>){code}morecode'
 perl -ne 'code;END{morecode}'
 perl -ne 'code}{morecode'

-p і неперевершені фігурні дужки

Аналогічно вищевикладеному, -pперемикач обмотається while (<>) { ... ; print }навколо програми.

Використовуючи незрівняні фігурні дужки, perl -p 'code}{morecode'буде надруковано лише один раз після виконання codeдля всіх рядків введення, після чого morecode.

Оскільки $_це не визначено при morecode;printвиконанні, роздільником записів виводу $\можна зловживати для друку фактичного виводу.

Наприклад

perl -pe '$\+=$_}{'

зчитує одне число на рядок із STDIN і друкує їх суму.


Я припускаю, що ви могли це зробити #!perl -nна першому рядку, правда?
ASCIIThenANSI

@ASCIIThenANSI: Так, це правильно. (Вибачте за пізню відповідь.)
Денніс

1
Надаючи кредит там, де належить кредит, я думаю, що вперше в одному з відповідей @primo 's Perl я побачив поєднання цих трьох хитрощів (незрівнянні фігурні дужки -pта $\​) . Прочитати його відповіді - це гарна порада Perl самостійно.
Денніс

1
Закидання }for(...){між брекетами також дуже зручно, наприклад, codegolf.stackexchange.com/a/25632
primo

Одне, що я вважаю корисним у поєднанні з -n - це || = оператор присвоєння значення за замовчуванням. Збирається навколо не в змозі призначити значення перед циклом.
дельтарай

18

Використовуйте $_для усунення скалярних посилань. Це спеціальна змінна, яка використовується за замовчуванням у більшості функцій, і лише вихід параметрів є ярликом для посилання на цю змінну.

Змінившись $nна $_, ви можете змінитись $n=<>;chop$n;print$nна$_=<>;chop;print

Тут printфункція друкує вміст $_за замовчуванням, а chopтакож працює $_.


Є чи $_=<>;потрібно, чи не <>;читати рядки в $_автоматично?
sundar

Ні, я не думаю. Я порівняв програми $_=<>;printі <>;print. Перший повторює мені те, що я набираю, а другий - ні.
PhiNotPi

О, дякую, виявляється, це відбувається лише у таких випадках print while(<>). Не впевнений, чи це окремий випадок, чи є якась цілісна логіка, за якою ні одна <>з частин, perlopні whileчастина perlsynне згадують про цю поведінку.
sundar

1
@sundar while(<>)- це особливий випадок, задокументований в perlsyn, Операторах вводу-виводу: "Якщо і лише тоді, якщо символ введення є єдиним, що знаходиться всередині умовного твердження" while "(навіть якщо маскується під" for (;;) " цикл), значення автоматично присвоюється глобальній змінній $ _, знищуючи все, що там було раніше ".
kernigh

17

Використовуйте спеціальні змінні Perl, де це можливо, наприклад:

  • Використовуйте $"замість" "
  • Використовуйте $/замість"\n"

Вони мають додаткову перевагу - гарантований однозначний довгий ідентифікатор, за допомогою лексеру. Це дає змогу склеїти його до ключового слова, яке слідує за ним, як у:print$.for@_

Список усіх спеціальних змінних доступний тут: Спеціальні змінні


15

Не використовуйте qw. Це відходи двох символів, які можна було б використовувати краще. Наприклад, не пишіть таке.

@i=qw(unique value);

Замість цього використовуйте голові слова.

@i=(unique,value);

Або якщо ви не можете використовувати бареври, використовуйте globсинтаксис.

@i=<unique value>;

glob синтаксис також можна використовувати для цікавих ефектів.

@i=<item{1,2,3}>;

12

Використовуйте модифікатори операторів замість складених операторів.

Складені оператори, як правило, вимагають дужок для аргументу та дужок для блоку, тоді як модифікатори висловлювань не потребують жодного.

Порівняйте:

  • $a++,$b++while$n-- проти while($n--){$a++;$b++}
  • chop$,if$c проти if($c){chop$,}

Зауважте, що останній приклад пов'язується з $c&&chop$,, але починає дійсно світитись для більшості операцій із кількома операторами. В основному все, що втрачає перевагу оператора перед &&.


11

Не варто use strict. (не цитуйте мене з цього приводу, важливо, що це стосується контексту PCG.SE) І, що ще важливіше, не кодуйте як би під суворим. Звичайні підозрювані:

  • не myдекларуйте змінні, якщо ви можете цього уникнути. Єдині змінні, які справді потребують, - myце ті, які ви хочете лексично оцінити. Це навряд чи будь-який з них при гольфі, де вам не потрібен захист від розмаху і прагнете повністю контролювати рекурсію.
  • не цитуйте рядки з одним словом: ( приклад ). Однак переконайтеся, що у вас немає функції з тим самим іменем.

5
print helloне буде працювати. Це насправді означає print hello $_(роздрукувати $_у файл-файлі hello).
Конрад Боровський

@GlitchMr спасибі! (і тепер я розбився, бо моя думка досі справедлива, просто не з print, і тепер я не можу знайти приємний і короткий приклад)
JB

@JB ось хороший приклад: codegolf.stackexchange.com/a/8746/3348
ardnew

11

Я впевнений, що деякі з них мають формальні імена, і я просто не знаю їх.

  • Якщо у вас є цикл "час" (або цикл "для циклу", який ви можете зробити в циклі "час"), ви можете визначити "час" після команди: print $n++ while ($n < 10)
  • Якщо вам потрібно прочитати все з STDIN в рядок: $var = join('',<>)
  • Як вказував CeilingSpy, використання $ / замість \ n у деяких ситуаціях швидше: print ('X'*10) . "\n";довше, ніжprint ('X'*10) . $/;
  • sayФункція Perl коротше print, але код доведеться -Eзамість цього запускати-e
  • Використовуйте діапазони на кшталт a..zабо рівні aa..zz. Якщо потрібно як рядок, використовуйте join.
  • Збільшення рядків: $z = 'z'; print ++$z;відобразитьсяaa

Це все, що я можу зараз придумати. Я можу додати ще трохи пізніше.


1
Що print ('X'*10) . $/;робити? Для мене він друкує 0і не містить нового рядка. З одного боку, круглі дужки стають аргументом виклику в стилі функції print, який пов'язує міцніше .. А ти мав на увазі xзамість цього *чи щось?
aschepler

В Postfix не потрібні дужки while, join'',<>;також без них працює.
choroba

10

Використовуйте несловні символи як імена змінних

Використання $%замість $aможе дозволити вам встановити ім'я змінної прямо поруч з if, forабо whileпобудувати , як в:

@r=(1,2,3,4,5);$%=4; print$_*$%for@r

Можна скористатися багатьма, але перевірте документи та відповідь @ BreadBox, які з них мають магічні ефекти!


Використовуйте карту, коли ви не можете використовувати модифікатори операторів

Якщо ви не можете використовувати модифікатори висловлювань відповідно до відповіді @ JB , карта може зберегти байт:

for(@c){} vs. map{}@c;

і корисно, якщо ви хочете зробити вкладені ітерації, оскільки ви можете помістити forпетлі постфікса всередині map.


Використовуйте всі магічні змінні Regular Expression

У Perl є магічні змінні для "текст перед матчем" та "текст після матчу", тому можна розділити на групи з двох із потенційно меншими символами:

($x,$y)=split/,/,$_;
($x,$y)=/(.+),(.+)/;
/,/; # $x=$`, $y=$'
# Note: you will need to save the variables if you'll be using more regex matches!

Це також може добре працювати як заміна substr:

$s=substr$_,1;
/./;# $s=$'

$s=substr$_,4;
/.{4}/;# $s=$'

Якщо вам потрібен вміст збігу, $&можна використовувати, наприклад:

# assume input like '10 PRINT "Hello, World!"'
($n,$c,$x)=split/ /,$_;
/ .+ /; # $n=$`, $c=$&, $x=$'

Замініть абонементи на довгі імена на коротші назви

Якщо ви дзвоните скажіть printу своєму коді чотири і більше разів (очевидно, це залежить від тривалості звичайної програми, яку ви телефонуєте), замініть його на коротше під ім'я:

sub p{print@_}p;p;p;p

vs.

print;print;print;print

Замініть умовні інкременти / декременти

Якщо у вас є код типу:

$i--if$i>0

Ви можете використовувати:

$i-=$i>0

замість цього зберегти кілька байт.


Перетворити на ціле число

Якщо ви не присвоюєте змінну і не можете використовувати підказку хлібопекарської скриньки , ви можете використовувати вираз 0|:

rand 25 # random float eg. 19.3560355885212

int rand 25 # random int

0|rand 25 # random int

rand 25|0 # random int

~~rand 25 # random int

Однак варто зазначити, що для доступу до індексу масиву вам не потрібно використовувати ціле число:

@letters = A..Z;
$letters[rand 26]; # random letter

9

redoдодає поведінку циклу до блоку без forабо while. {redo}є нескінченною петлею.


7

Не скопіюйте в скобках функції функції.

Perl дозволяє викликати відому (основну або попередньо визначену) функцію за допомогою NAME LISTсинтаксису. Це дозволяє скинути &сигіл (якщо ви його ще використовували), а також круглі дужки.

Наприклад: $v=join'',<>

Повна документація


5

Спробуйте використовувати значення виразу призначення, наприклад:

# 14 characters
$n=pop;$o=$n&1

# 13 characters, saves 1
$o=($n=pop)&1

Це працює тому $n, що в Perl є 2 символи. Ви можете безкоштовно змінити $nзначення ()та зберегти 1 крапку з комою, перемістивши завдання в круглі дужки.


5

Ви можете запускати кілька різних операторів у вкладеній потрійній логіці.

Припустимо , у вас є великий if- elsifзаява. Це може бути будь-яка логіка та будь-яка кількість тверджень.

if( $_ < 1 ) {
    $a=1;
    $b=2;
    $c=3;
    say $a.$b.$c;
} elsif($_ < 2 ) {
    $a=3;
    $b=2;
    $c=1;
    say $a.$b.$c;
} elsif( $_ < 3) {
    $a=2;
    $b=2;
    $c=2;
    say $a.$b.$c;
}

Ви можете використовувати (cmd1, cmd2, cmd3)всередині потрійного оператора для запуску всіх команд.

$_ < 1 ?
    ($a=1,$b=2,$c=3,say$a.$b.$c):
$_ < 2 ?
    ($a=3,$b=2,$c=1,say$a.$b.$c):
$_ < 3 ?
    ($a=2,$b=2,$c=2,say$a.$b.$c):
0; #put the else here if we have one

Ось фіктивний приклад:

perl -nE'$_<1?($a=1,$b=2,$c=3,say$a.$b.$c):$_<2?($a=3,$b=2,$c=1,say$a.$b.$c):$_<3?($a=2,$b=2,$c=2,say$a.$b.$c):0;' <(echo 2)

4

Використовуйте select(undef,undef,undef,$timeout)замістьTime::HiRes

(Взято з https://stackoverflow.com/a/896928/4739548 )

Багато проблем вимагають від вас спати з більшою точністю, ніж цілі числа. select()Аргумент тайм-аута може зробити саме це.

select($u,$u,$u,0.1)

набагато ефективніше, ніж:

import Time::HiRes qw(sleep);sleep(0.1)

Перший - всього 20 байт, тоді як останній займає 39. Однак перший вимагає, що ви не використовуєте його $uі ніколи не визначали його.

Якщо ви збираєтеся використовувати його багато, імпорт Time::HiResокупається, але якщо він вам знадобиться лише один раз, використовуючи select($u,$u,$u,0.1)економію 19 байт, що, безумовно, є поліпшенням у більшості випадків.


1

Скоротіть друковані заяви

Якщо викликом не визначено інакше, вам не знадобляться нові рядки.
У нашому "виклику" сказано: "виведіть випадкове число від 0 до 9 до STDOUT". Ми можемо взяти цей код (28 байт):

$s=int(rand(10));print"$s\n"

І скоротити його до цього (25 байт):

$s=int(rand(10));print $s

просто надрукувавши змінну. Останній стосується лише цього виклику (19 байт):

print int(rand(10))

але це працює лише тоді, коли вам не потрібно нічого робити зі змінною між призначенням і друком.


Останній тут уже перерахований .
Sp3000

@ Sp3000 Дякую, я оновив свою відповідь.
ASCIIThenANSI

1

Використовуйте глобуси, як рядкові букви

Іноді (часто, коли ви маєте справу з проблемами, пов’язаними з або ), ви отримуєте велику користь від можливості гніздування рядкових літералів. Зазвичай, ви б це зробили q(…). Однак, залежно від того, які символи вам потрібні всередині рядка, ви, можливо, зможете зберегти байт та використати <…>глобальний оператор. (Зверніть увагу, що те, що знаходиться у кутових дужках, не може виглядати як файл файлів і не може виглядати так, що це призначено для розширення до списку імен файлів, а це означає, що досить багато символів не працюватимуть правильно.)


0

Використовуйте вираження regexe.

Гідною ілюстрацією цього є наступний код, що формує вхід в синусоїду:

s/./print" "x(sin($i+=.5)*5+5).$&/eg;

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.