Tips for golfing in Perl?

47

21

What general tips do you have for golfing in Perl? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to Perl (e.g. "remove comments" is not an answer). Please post one tip per answer.

commando

Posted 2012-03-09T14:53:19.853

Reputation: 1 053

Answers

26

TMTOWTDI

That's the most important Perl golfing tip you need to know. Whenever you're looking at some too-long sequence of characters you absolutely have to have in order to accomplish your task, ask yourself if there isn't some other way to get the same effect using a different feature. There usually is. Here are just a handful:

  • ~~ enforces a scalar context and is 4 chars shorter than scalar.

  • y///c is one char shorter than length when getting the length of $_.

  • Need to iterate over the chars in $_? Replace split// with /./gs. (Or use /./g if you also want to skip newlines.) This works with other variables: replace split//,$x with $x=~/./gs.

  • Every Perl builtin returns something. print returns 1, for example, to indicate successful I/O. If you need to initialize $_ to a true value, for example, $_=print$foo allows you to kill two birds with one stone.

  • Almost every statement in Perl can be written as an expression, allowing it to be to used in a wider variety of contexts. Multiple statements can be become multiple expressions chained together with commas. Tests can be done with short-circuiting operators ?: && ||, and also with and and or, which do the same thing but with precedence lower than all other operators (including assignment). Loops can be done via map or grep. Even keywords like next, last and return can be used in an expression context, even though they don't return! Keeping these kinds of transformations in mind give you opportunities to replace code blocks with expressions that can be stuffed into a wider variety of contexts.

breadbox

Posted 2012-03-09T14:53:19.853

Reputation: 6 893

$_=print"" is shorter than $_=print$foo. – ASCIIThenANSI – 2015-12-18T18:23:13.960

5The idea is that you already need to print $foo. Otherwise, $_=1 is much shorter than $_=print"" and has the same effect. – breadbox – 2016-02-19T23:59:13.213

3For the third do you mean iterating over the chars in $x? Otherwise you could just do /./gs and /./g. – redbmk – 2016-04-14T10:44:10.163

25

Abuse Perl's special variables!

  • As noted in a previous answer $/ and $" are initialized by default to "\n" and " ", respectively.

  • $, and $\ are both set to undef by default, and are 3 chars shorter.

  • Setting $\ to a value will cause it to be appended to every print. For example: perl -ple '$\="=".hex.$/' is a handy hex-to-decimal converter.

  • If you're not reading files from the command-line, you can use the -i command-line switch as an extra channel for inputting a string. Its value will be stored in $^I.

  • $= forces whatever is assigned to it to be an integer. Try running perl -ple '$_=$==$_' and giving it various inupts. Likewise, $- forces its value to be a non-negative integer (i.e. a leading dash is treated as a non-numeric character).

  • You can use $. as a boolean flag that is automatically reset to a true (nonzero) value on every iteration of a while(<>) loop.

breadbox

Posted 2012-03-09T14:53:19.853

Reputation: 6 893

20

-n and unmatched curly brackets

It is well known that the command line switch -n can be used to execute the script once for every line.

perl --help says:

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

What it doesn't say explicitly is that Perl doesn't just assume a loop around the program; it literally wraps while (<>) { ... } around it.

This way, the following commands are equivalent to each other:

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

-p and unmatched curly brackets

Similarly to the above, the -p switch wraps while (<>) { ... ; print } around the program.

By using unmatched curly brackets, perl -p 'code}{morecode' will only print once after executing code for all lines of input, followed by morecode.

Since $_ is undefined when morecode;print is executed, the output record separator $\ can be abused to print the actual output.

For example

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

reads one number per line from STDIN and prints their sum.

Dennis

Posted 2012-03-09T14:53:19.853

Reputation: 196 637

I'm assuming you could accomplish this with #!perl -n on the first line, right? – ASCIIThenANSI – 2015-05-02T16:56:43.070

@ASCIIThenANSI: Yes, that is correct. (Sorry for the late answer.) – Dennis – 2015-06-14T15:22:06.620

1

Giving credit where credit is due, I think I saw the combination of these three tricks (unmatched curly brackets, -p and $\​) for the first time in one of @primo's Perl answers. To read his answers is a good Perl tip on its own.

– Dennis – 2015-06-14T15:24:26.953

1

Throwing a }for(...){ in between the braces is often quite handy as well, e.g. http://codegolf.stackexchange.com/a/25632/

– primo – 2015-10-02T09:53:49.377

One thing I found useful in conjunction with -n is the ||= default value assignment operator. Gets around not being able to assign a value before the loop. – deltaray – 2016-12-17T05:55:02.983

18

Use $_ to eliminate scalar references. It is the special variable that is used as a default by most functions, and just leaving out parameters is a shortcut to reference this variable.

By changing $n to $_, you can change $n=<>;chop$n;print$n to $_=<>;chop;print

Here, the print function prints the contents of $_ by default, and chop also works on $_.

PhiNotPi

Posted 2012-03-09T14:53:19.853

Reputation: 26 739

Is $_=<>; required, doesn't <>; read the line into $_ automatically? – sundar - Reinstate Monica – 2013-08-20T18:55:53.733

No, I don't think so. I compared the programs $_=<>;print and <>;print. The first one repeats back to me what I type, while the other one doesn't. – PhiNotPi – 2013-08-21T21:22:25.420

Oh, thanks, turns out that happens only in cases like print while(<>). Not sure if it's a special case or there's some coherent logic behind it, neither <>'s part in perlop nor while part of perlsyn seem to mention this behaviour. – sundar - Reinstate Monica – 2013-08-21T22:36:17.150

1@sundar while(<>) is a special case, documented in perlsyn, I/O Operators: 'If and only if the input symbol is the only thing inside the conditional of a "while" statement (even if disguised as a "for(;;)" loop), the value is automatically assigned to the global variable $_, destroying whatever was there previously." – kernigh – 2014-11-09T00:22:20.717

17

Use Perl's special variables where-ever you can, eg:

  • Use $" instead of " "
  • Use $/ instead of "\n"

They have the added benefit of being a guaranteed one-character long identifier, with help from the lexer. This makes it possible to glue it to the keyword following it, as in: print$.for@_

The list of all the special variables is available here: Special Variables

3aw5TZetdf

Posted 2012-03-09T14:53:19.853

Reputation: 387

15

Don't use qw. This is waste of two characters that could be used in better way. For example, don't write the following.

@i=qw(unique value);

Instead use barewords.

@i=(unique,value);

Or if you cannot use barewords, use glob syntax.

@i=<unique value>;

glob syntax can also be used for interesting effects.

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

Konrad Borowski

Posted 2012-03-09T14:53:19.853

Reputation: 11 185

12

Use statement modifiers instead of compound statements.

Compound statements tend to require parentheses for the argument and braces for the block, whereas statement modifiers need neither.

Compare:

  • $a++,$b++while$n-- vs while($n--){$a++;$b++}
  • chop$,if$c vs if($c){chop$,}

Note that the last example ties with $c&&chop$,, but starts really shining for most multi-statement operations. Basically anything that loses operator precedence to &&.

J B

Posted 2012-03-09T14:53:19.853

Reputation: 9 638

11

I'm sure some of these have formal names and I'm just not aware of them.

  • If you have a while loop (or a for loop you can make into a while loop) you can define the "while" after the command: print $n++ while ($n < 10)
  • If you need to read everything from STDIN into a string: $var = join('',<>)
  • As CeilingSpy pointed out, using $/ instead of \n is faster in some situations: print ('X'*10) . "\n"; is longer than print ('X'*10) . $/;
  • Perl's say function is shorter than print, but you'll have to run the code with -E instead of -e
  • Use ranges like a..z or even aa..zz. If needed as a string, use join.
  • Incrementing strings: $z = 'z'; print ++$z; will display aa

That's all I can think of right now. I may add some more later.

Mr. Llama

Posted 2012-03-09T14:53:19.853

Reputation: 2 387

1What is print ('X'*10) . $/; supposed to do? For me it prints 0 and no newline. For one thing, the parentheses become a function-style call argument to print, which binds tighter than .. And did you mean x instead of * or something? – aschepler – 2017-07-30T00:45:37.223

No parentheses needed in postfix while, join'',<>; also works without them. – choroba – 2017-10-21T23:36:28.960

11

Don't use strict. (don't quote me on this, PCG.SE context kinda matters) And, more importantly, don't code as if under strict. The usual suspects:

  • don't my-declare variables if you can avoid it. The only variables that really need my are those you want lexically scoped. That's barely any of them when golfing, where you don't need scope protection and tend to fully control recursion.
  • don't quote one-word strings: (example). Do ensure you don't have a function with the same name, though.

J B

Posted 2012-03-09T14:53:19.853

Reputation: 9 638

5print hello won't work. It actually means print hello $_ (print $_ to filehandle hello). – Konrad Borowski – 2012-10-22T12:25:32.243

@GlitchMr thanks! (and now I'm bummed, because my point is still valid, just not with print, and now I can't find a nice and short example) – J B – 2012-10-22T18:10:20.357

@JB here's a good example: http://codegolf.stackexchange.com/a/8746/3348

– ardnew – 2012-10-22T19:45:21.280

10

Use non-word characters as variable names

Using $% instead of $a can allow you to place the variable name right next to an if, for or while construct as in:

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

Many can be used, but check the docs and @BreadBox's answer for which ones have magic effects!


Use map when you can't use statement modifiers

If you can't use statement modfiers as per @JB's answer, map might save a byte:

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

and is useful if you want to do nested iterations as you can put postfix for loops inside the map.


Use all the magic Regular Expression variables

Perl has magic variables for 'text before match' and 'text after match' so it is possible to split to groups of two with potentially fewer characters:

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

This might also work well as a replacement for substr:

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

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

If you need the contents of the match, $& can be used, eg:

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

Replace subs with long names with a shorter name

If you call say print four or more times in your code (this obviously varies with the length of the routine you're calling), replace it with a shorter sub name:

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

vs.

print;print;print;print

Replace conditional incrementors/decrementors

If you have code like:

$i--if$i>0

you can use:

$i-=$i>0

instead to save some bytes.


Convert to integer

If you aren't assigning to a variable and so can't use breadbox's tip, you can use the expression 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

It's worth noting however than you don't need to use an integer to access an array index:

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

Dom Hastings

Posted 2012-03-09T14:53:19.853

Reputation: 16 415

9

redo adds loop behavior to a block without for or while. {redo} is an infinite loop.

user130144

Posted 2012-03-09T14:53:19.853

Reputation: 303

7

Don't parenthesize function calls.

Perl lets you call a known (core or predeclared) function using the NAME LIST syntax. This lets you drop the & sigil (if you were still using it) as well as the parentheses.

For example: $v=join'',<>

Full documentation

J B

Posted 2012-03-09T14:53:19.853

Reputation: 9 638

5

Try to use the value of an assignment expression, like so:

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

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

This works because $n is 2 characters in Perl. You may change $n to () at no cost, and save 1 semicolon by moving the assignment into the parentheses.

kernigh

Posted 2012-03-09T14:53:19.853

Reputation: 2 615

5

You can run multiple different statements within nested ternary logic.

Suppose you have a big if-elsif statement. This could be any logic and any number of statements.

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;
}

You can use (cmd1, cmd2, cmd3) inside the ternary operator to run all of the commands.

$_ < 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

Here's a dummy example:

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)

hmatt1

Posted 2012-03-09T14:53:19.853

Reputation: 3 356

4

Use select(undef,undef,undef,$timeout) instead of Time::HiRes

(Taken from https://stackoverflow.com/a/896928/4739548)

Many challenges require you to sleep with greater precision than integers. select()'s timeout argument can do just that.

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

is much more efficient than:

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

The former is only 20 bytes, whereas the latter takes up 39. However, the former requires you aren't using $u and have never defined it.

If you're going to use it a lot importing Time::HiRes pays off, but if you only need it once, using select($u,$u,$u,0.1) saves 19 bytes, which is definitely an improvement in most cases.

ASCIIThenANSI

Posted 2012-03-09T14:53:19.853

Reputation: 1 935

1

Use globs like string literals

Occasionally (often when dealing with or challenges) you benefit greatly from the ability to nest string literals. Normally, you'd do this with q(…). However, depending on what characters you need inside the string, you may be able to save a byte and use <…>, the glob operator. (Note that what's inside the angle brackets can't look like a filehandle, and can't look like it's meant to be expanded into a list of filenames, which means that quite a lot of characters won't work correctly.)

user62131

Posted 2012-03-09T14:53:19.853

Reputation:

1

Shorten your print statements

Unless the challenge specifies otherwise, you don't need trailing newlines.
Our 'challenge' says 'output a random number from 0 to 9 to STDOUT'. We can take this code (28 bytes):

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

And shorten it to this (25 bytes):

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

by simply printing the variable. This last one only applies to this challenge specifically (19 bytes):

print int(rand(10))

but that only works when you don't have to do anything to the variable in between assignment and printing.

ASCIIThenANSI

Posted 2012-03-09T14:53:19.853

Reputation: 1 935

The last one's already listed here.

– Sp3000 – 2015-05-02T18:58:52.783

@Sp3000 Thank you, I've updated my answer. – ASCIIThenANSI – 2015-05-02T20:22:48.657

0

Use expression regexe.

Decent illustration of this is following code shaping the input into sine wave:

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

As you can see, it's pretty compact way of iterating over characters in standard input. You could use other regex to change the way how things are matched.

Krzysztof Szewczyk

Posted 2012-03-09T14:53:19.853

Reputation: 3 819