http://www.perlmonks.org?node_id=816630


in reply to The golf course looks great, my swing feels good, I like my chances (Part VI)

Shortly after posting this node, I noticed something baffling on the codegolf activity log:

128 ToastyX PHP Failed 130 ToastyX PHP Passed 127 ToastyX PHP Failed 129 ToastyX PHP Failed 129 ToastyX PHP Passed
What was ToastyX up to? If he'd read my node and wanted to catch up, why not just post 129? What's with the failed 127 and 128 attempts? Of course, it's perfectly possible he had not read my node and, Jasper-like, concocted a completely different 129 stroke solution. I had no way of knowing, but was alarmed by those failed 127 and 128 attempts. My previous golfing experience told me that failed attempts like these are frequently followed by a successful submission. To lose the lead now would be intolerable. What to do?

As is my custom in such emergencies, I stared at my 129 stroker:

<?for($r=fgets(STDIN).~XXXXXXXXXXXX;11^printf(~X..1*ord($r[6+$h]).s,($ +h^$r%12?g:p)&($h^"$r[3]$r[4]"/5?g:u)|~XXX);$h=12-$h-$d^=1);
looking for anything that makes me pull a face. The only thing that truly disgusted me was the hideous "$r[3]$r[4]" expression. Surely something can be done to shorten this. I started by rechecking the excellent wikipedia string function comparison page. Nope, PHP does not support string slices, a la Python and Ruby, no matter how much I'd like it to. The best I could find was substr($r,3) -- which is exactly the same length as before.

PHP function names tend to be quite verbose, so the best chance is surely to apply an operator to $r. What about our old friend, the string bitwise & operator? If I could apply this operator to convert the first three characters of $r to white space, while leaving the last two characters unchanged, that would do it.

To investigate this possibility, I started by running a little Perl program:

for my $i (48..58) { printf " %08b (ord %3d %c)\n", $i, $i, $i; }
to display the bit patterns of the characters of interest, namely:
00110000 (ord 48, "0" character) 00110001 (ord 49, "1" character) 00110010 (ord 50, "2" character) 00110011 (ord 51, "3" character) 00110100 (ord 52, "4" character) 00110101 (ord 53, "5" character) 00110110 (ord 54, "6" character) 00110111 (ord 55, "7" character) 00111000 (ord 56, "8" character) 00111001 (ord 57, "9" character) 00111010 (ord 58, ":" character)

Hmmm, all these characters can be converted to a space simply by bitwise and'ing them with the ord value 160 character. For example:

00111010 (ord 58, ":" character) & 10100000 (ord 160) = 00100000 (ord 32, space character)
Furthermore, all these characters are left unchanged by and'ing them with the ord value 255 character. For example:
00111001 (ord 57, "9" character) & 11111111 (ord 255) = 00111001 (ord 57, "9" character)

We can therefore transform, for example, "12:34" to "   34" via:

$r&XXXXX
where XXXXX is a string with ord values 160, 160, 160, 255, 255. Update: replacing 160 with 176 also works (converting to zeros rather than spaces) as was used by ToastyX.

In the context of a solution to this game, this trick saves two strokes, as shown below:

"$r[3]$r[4]" substr($r,3) ($r&XXXXX)

The shortest known PHP solution to this game is thus reduced from 129 to 127 strokes and has now drawn level with Python in the battle for the wooden spoon.

April 2012 Update

It's perfectly possible he had not read my node and, Jasper-like, concocted a completely different 129 stroke solution.
It turns out that is exactly what happened!

In fact, ToastyX found two completely different solutions, one of 129 strokes and one of 130.

His 129-stroke solution:

<?for($t=fgets(STDIN);23^$x=strpos(~GGGGGGGGGGGGGGGGGGGGGGGG,chr($i++) +);)echo($x--?$x^($t&XXXXX)/5?o:B:S)&($x^$t%12?$x<12?o:N:x);
is essentially a PHP version of the 103-stroke Perl solution described in detail at Spending Time on Saving Time [golf], where GGGGGGGGGGGGGGGGGGGGGGGG is a (bitwise negated) 24 character string with ord values 0, 8, 22, 40, 59, 77, 92, 102, 84, 63, 43, 26, 14, 9, 23, 24, 41, 42, 60, 61, 78, 79, 93, 103, XXXXX is a string with ord values 176, 176, 176, 255, 255, B is ord value 253 (i.e. "}" with the high bit set), S is ord value 160 (i.e. space with the high bit set), and N is ord value 138 (i.e. newline with the high bit set).

ToastyX deserved to win the game with this brilliant concoction because it can easily be shortened by five strokes simply by removing the chr() above. Maybe he knew PHP too well and didn't bother to look up the strpos function. As a PHP novice, I looked it up and was surprised to learn that, in addition to a character string, strpos also accepts an ord value as its second argument, thus making the chr() wrapping unnecessary.

His 130-stroke solution was:

<?for($c=S&$t=fgets(STDIN);$x-22;$c[ord(~$l[$x])]=($x^$t%12?$x<12?o:N: +x)&($x++^($t&XXXXX)/5?o:B))$l=GGGGGGGGGGGGGGGGGGGGGG?><?=$c;
where GGGGGGGGGGGGGGGGGGGGGG is a (bitwise negated) 22 character string with ord values 8, 22, 40, 59, 77, 92, 102, 84, 63, 43, 26, 14, 9, 23, 24, 41, 42, 60, 61, 78, 79, 93.

PHP String "autovivification"

In spirit, ToastyX's 130-stroke solution is akin to a two-dimensional array autovivification Perl entry, for example the 96-stroke:

$c[$_*=.52,5.5-4.7*cos][8+7.4*sin]=($_^$`%12?o:x)&($_^$'/5?o:'}')for<> +!~/:/..11;print"@$_ "for@c

PHP strings have an odd feature, illustrated by this test program:

$x = "A"; $x[5] = "Z"; echo "x='$x'\n";
which produces:
x='A Z'
That is, instead of failing with a "string index out of range" error, PHP "helpfully" extends the string, filling in the missing bit with spaces!

Note that this is different behavior to Ruby where:

x = "A" x[5] = "Z" print "x='#{x}'\n"
fails with:
`[]=': index 5 out of string (IndexError)
and Perl, where:
$x = "A"; substr($x,5,1) = "Z"; print "x='$x'\n";
fails with:
substr outside of string
Finally, Python strings are immutable, so this technique would appear to be out of the question there.

Curiously, Perl's vec function has similar behaviour to PHP strings, but fills in the empty bit with '\0' characters, not spaces.

So ToastyX's 130-stroke PHP solution can be loosely translated into Perl via:

vec($\,vec('GGGGGGGGGGGGGGGGGGGGGG',$_,8),8)=($_^$`%12?$_<12?111:10:12 +0)&($_^$'/5?111:125)for<>!~/:/..21;$\=~y/\0/ /;print
Of course, at 122 strokes, this is far too long to be a serious Perl golf entry.

Perl program to generate PHP solutions

For convenience, here is a Perl program that generates ToastyX's two PHP entries:

use strict; use warnings; my @twentytwoord = ( 8, 22, 40, 59, 77, 92, 102, 84, 63, 43, 26, 14, 9 +, 23, 24, 41, 42, 60, 61, 78, 79, 93 ); my @twentytwo = map { 255 - $_ } @twentytwoord; my @twentyfourord = ( 0, @twentytwoord, 103 ); my @twentyfour = map { 255 - $_ } @twentyfourord; print "twentytwoord: @twentytwoord\n"; print "twentytwo: @twentytwo\n"; print "twentyfourord: @twentyfourord\n"; print "twentyfour: @twentyfour\n"; my $lookup24str = join "", map { chr } @twentyfour; my $lookup22str = join "", map { chr } @twentytwo; my $prog129 = '<?for($t=fgets(STDIN);23^$x=strpos(~' . $lookup24str . ',chr($i++));)echo($x--?$x^($t&' . chr(176).chr(176).chr(176).chr(255).chr(255) . ')/5?o:' +. chr(253) . ':' . chr(160) . ')&($x^$t%12?$x<12?o:' . chr(138) . ':x);'; my $prog130 = '<?for($c=' . chr(160) . '&$t=fgets(STDIN);$x-22;$c[ord(~$l[$x])]=($x^$t%12?$x<12 +?o:' . chr(138) . ':x)&($x++^($t&' . chr(176).chr(176).chr(176).chr(255).chr(255) . ')/5?o:' +. chr(253) . '))$l=' . $lookup22str . '?><?=$c;'; for my $prog ($prog129, $prog130) { my $progfile = 'gen' . length($prog) . '.php'; open my $fh, '>', $progfile or die "error: open '$progfile': $!"; binmode $fh; print {$fh} $prog; close $fh; warn "created $progfile, ", length($prog), " bytes\n"; }

Replies are listed 'Best First'.
Re^2: The golf course looks great, my swing feels good, I like my chances (Part VI)
by doc_faustroll (Scribe) on Jan 19, 2010 at 23:34 UTC

    You are absolutely, delightfully nuts, but I gather you both know and hear that often. Thanks for making me smile.