Perl golf

My friend Stephen has posted a little challenge over on his blog. Basically it's to put a wrapper arond cal(1) so that if it is given a single argument between 1 and 12 or 'now' it prints a 3-month calendar centered around that month in the year, rather than the default behaviour which is to print a calendar for the year 1 through 12 - duh!

I of course took it as a perl golf challenge to produce the smallest (and therefore most unreadable) version possible. I know a few of my friends in perl-land read this blog, so perhaps you can come up with something even smaller than my current 404-byte version? To be fair, obvious hacks such as removing the die or shortening the /usr/bin/cal path are considered to be cheating ;-)

Here's my current effort, line breaks added to stop it sending your browser mental.

$c='/usr/bin/cal';($m,$y)=(localtime)[4,5];$m+=1;$y+=1900;$_=$ARGV[0]||'';
if($_ eq'now'){$a[1]=[$m,$y]}elsif($_=~/^\d+$/&&$_>=1&&$_<=12){$a[1]=[$_,$y]}
else{exec($c,@ARGV)||die($!)}$_=$a[1][0]-1;
@{$a[0]}=$_==0?(12,$a[1][1]-1):($_,$a[1][1]);$_=$a[1][0]+1;
@{$a[2]}=$_==13?(1,$a[1][1]+1):($_,$a[1][1]);
@c=map[map{chomp;$_}`$c @$_`],@a;printf"%-20s   %-20s   %-20s\n",
map shift@$_||'',@c for 1..7

Can you do better?

Update

Thanks to Jason Santos for spotting a bug, and being devious enough to get the count down even further - here's the latest version, see if you can spot the differences:

#!/bin/perl
$c='/usr/bin/cal';($m,$y)=(localtime)[4,5];$m++;$y+=1900;$_=$ARGV[0]||'';
if(/^now$/){$a[1]=[$m,$y]}elsif(/^\d+$/&&$_>=1&&$_<=12){$a[1]=[$_,$y]}
else{exec($c,@ARGV)||die$!}$_=$a[1][0]-1;
@{$a[0]}=$_==0?(12,$a[1][1]-1):($_,$a[1][1]);$_=$a[1][0]+1;
@{$a[2]}=$_==13?(1,$a[1][1]+1):($_,$a[1][1]);
@c=map[map{chop;$_}`$c @$_`],@a;printf"%-20s   %-20s   %-20s\n",
map shift@$_||'',@c for 1..8
Tags : , ,
Categories : Tech, Perl, Work


Re: Perl golf

I've shortened yours a bit (384 bytes), and fixed an error (you only printed out the first seven lines, but some months have eight lines... e.g., your version omits Haloween this year!):
$c='/usr/bin/cal';($m,$y)=(localtime)[4,5];$m++;$y+=1900;$_=$ARGV[0]||'';if(/^now$/){$a[1]=[$m,$y]}
elsif(/^\d+$/&&$_>=1&&$_<=12){$a[1]=[$_,$y]}else{exec($c,@ARGV)||die$!}$_=$a[1][0]-1;
@{$a[0]}=$_==0?(12,$a[1][1]-1):($_,$a[1][1]);$_=$a[1][0]+1;@{$a[2]}=$_==13?(1,$a[1][1]+1):($_,$a[1][1]);
@c=map[map{chop;$_}`$c @$_`],@a;printf"%-20s   %-20s   %-20s\n",map shift@$_||'',@c for 1..8

Re: Perl golf

Oops, thanks for catching the error! I like the use of a RE against the default $_ operand instead of eq, and the substitution of chop for chomp was *very* sneaky ;-)

Re: Perl golf

How about this one, in 353 bytes.
#!/bin/perl
$c='/usr/bin/cal';($m,$y)=(localtime)[4,5];$m++;$y+=1900;$_=$ARGV[0];
$a[1]=[/^now$/?$m:/^\d+$/&&$_>=1&&$_<=12?$_:(exec($c,@ARGV)||die$!),$y];$_=$a[1][0]-1;
@{$a[0]}=$_?($_,$a[1][1]):(12,$a[1][1]-1);$_+=2;
@{$a[2]}=$_==13?(1,$a[1][1]+1):($_,$a[1][1]);
@c=map[map{chop;$_}`$c @$_`],@a;printf"%-20s   %-20s   %-20s\n",map shift@$_,@c for 1..8

Re: Perl golf

Yep, that works for me, thanks! I suspect there isn't much more fat left to be trimmed...

Re: Perl golf

308 and cleaner:

<code>#!/bin/perl
$c='/usr/bin/cal';$_=shift;@m=split" ",`date '+%m %Y'`;
/^now$/||/^(1[012]?|[2-9])$/&&($m[0]=$1)||exec($c,@ARGV)||die$!;
@l=@n=@m;--$l[0]||(--$l[1],$l[0]=12);++$n[0];($n[0]%=13)||(++$n[0],++$n[1]);
@c=map[map{chop;$_}`$c @$_`],\(@l,@m,@n);printf"%-20s   %-20s   %-20s\n",map shift@$_,@c for 1..8</code>