Beefy Boxes and Bandwidth Generously Provided by pair Networks Joe
P is for Practical
 
PerlMonks  

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

by eyepopslikeamosquito (Canon)
on Dec 30, 2009 at 10:14 UTC ( #814900=perlmeditation: print w/ replies, xml ) Need Help??

I like trying to win. That's what golf is all about ... Resolve never to quit, never to give up, no matter what the situation.

-- Jack Nicklaus

Ever since Elin came into my life, things just became a lot better. Someone you can bounce things off, somebody who is a great friend. We do just about everything together. It's nice having that type of person around you. She's so much like me. She's very competitive, very feisty, just like I am.

-- Tiger Woods

Curiously, in addition to code golf, I've enjoyed a life long passion for real, physical golf. Though I can't compete with Tiger Woods (either on or off the golf course), my golf was once strong enough to represent my club in pennant matches. I fondly remember competing against other clubs in the good ol' days, especially punching the air with my fist while eyeballing my match play opponent after holing a crucial putt from the edge of the green. Though unable to physically eyeball my code golf opponents nowadays, I do frequently indulge in Tiger-esque fist pumps while code golfing at my desk, sometimes even leaping out of my chair to do so.

By all accounts though, I'm nowhere near as competitive, or feisty, as Tiger Woods and his wife Elin. Nonetheless, I am a very competitive person. So, while acknowledging the learning aspect of the game, and appreciating its artistic side, the main attraction of code golf, for me, has always been fun and competition.

And this game was chock full of stimulating competition. Long before Jasper began his historic Perl whittle, my competitive juices were squirting at the Python and PHP leaderboards: Python because of an even more relentless whittler than Jasper, namely Norway's leading golfer, hallvabo; PHP because I found myself locked in a duel with legendary Texan PHP golfer ToastyX, winner of 15 of 16 PHP golf contests, often by ridiculous margins.

First to Python and hallvabo. Last April, just hours after I posted my Roman article, hallvabo, who had been breathing down my neck just one stroke off the pace, sprinted past me snatching the Python lead from my clenched fists, which were now shaking in fury in the general direction of Norway and its majestic fjords.

Indeed, if my best Perl solution:

print$"x(318%$_/9),(($_-$`)%12?o:x)&($_%12^$'/5?o:'}'),$/x($_/85)for u +npack<>!~/:/.C12,'XXXXXXXXXXXX'
doesn't look very Perlish, it's because it was just a routine translation of my hallvabo-provoked Python solution.

By contrast, Jasper's best:

$c[$_*=.52,5.5-4.7*cos][8+7.4*sin]=$`%12-$_?$_^$'/5?o:'m':$_^$'/5?h:x for<>!~/:/..11;print"@$_ "for@c
looks much more Perlish to me, exploiting three of Perl's distinctive features: autovivification; space insertion between array elements when string interpolating; and default arguments to Perl built-ins. And Jasper's proved to be the superior Perl golf approach.

Alas, Jasper's beautiful and elegant Perl solution transmogrifies into a grotesque monster when translated into any of the other three languages: no autovivification; no default arguments to built-ins; no insertion of spaces between interpolated array elements; sin and cos built-ins AWOL in Python and Ruby; and PHP's array semantics just too weird for words. And that's just listing the obvious horrors.

Finding the "right" golfic approach for each language is something of a dark art and there is precious little advice available on this difficult topic. Shed light on this dark side of programming, this article will.

Before revealing my shortest Ruby, Python and PHP solutions therefore, I'll compare and contrast how different aspects of this game are best coded in each of the four languages.

Reading and Converting Hours and Minutes from Stdin

Let's begin by analysing a basic part of any solution to this game: reading the digital time from stdin and converting to a 0..11 clock face mark. In all four languages, this aspect of my solutions remained essentially unchanged throughout this competition.

TechniquePerlRubyPythonPHP
Reading HH:MM<>=~/:/r=*$<r=raw_input()$r=fgets(STDIN)
Extracting hours$`%12r.to_i%12int(r[:2])%12$r%12
Extracting minutes$'/5r[3,2].to_i/5int(r[3:])/5"$r[3]$r[4]"/5
Number of strokes16273834

Of all the aspects of this game, this one most clearly demonstrates why Perl and Ruby have defeated Python and PHP in 27 of 28 games played at the codegolf web site. Most codegolf challenges require you to read from stdin and there is simply no short way to do that in Python and PHP.

Note that Python is the only language whose string to int conversion routine refuses to convert the string "12:" to the integer 12, forcing you to employ r[:2], rather than plain old r, at a cost of four strokes. While this extra level of strictness may well be a boon when writing production code, it's an irritant when golfing.

Note too how many strokes the Ruby and Python string to int conversions consume. Were it not for this handicap, I suspect Ruby would mostly out-golf Perl.

Eliminating Jasper's "god-awful ternary"

In case you've forgotten, and as discussed last time, Jasper's infamous "god-awful ternary" is:

$`%12-$_?$_^$'/5?o:'m':$_^$'/5?h:x
Eliminating this eyesore was a fascinating challenge in this game in all four languages.

TechniquePerlRubyPythonPHP
Jasper's eyesore($_^$h?o:x)&($_^$m?o:'}')'ohmx'[1&1>>i-h^2&2<<i-m,1]'ohxm'[-(i==m)^(i==h)]($i^$h?o:x)&($i^$m?o:'}')
Number of strokes25272225

As will be seen later when the shortest solutions for each language are revealed, the above table is quite crude in that it does not take account of the many tactical wrinkles available in this game. Having said that, it does help us understand the fundamentals of each language. In particular, note that:

  • String bitwise operators behave essentially identically in Perl and PHP.
  • Python does not support string bitwise operators. You can fake them, but you don't want to. No, really you don't: chr((i^h and 111or 120)&(i^m and 111or 125)). Python's lack of a short ?: operator is fatal here. These sorts of problems are almost always better golfed using Python's powerful and concise string slices, in this case: 'ohmx'[expr].
  • Though Ruby does not support string bitwise operators, you can fake them with: (i==h ??x:?o)&(i==m ??}:?o). The two stroke ?o (rather than 111) combined with a short ?: operator makes this approach more attractive than in Python. Nonetheless, shorter Ruby alternatives are available via 'ohmx'[expr]. Note that i==h above cannot be shortened to i^h or i-h, as it was in the other languages, because zero evaluates to true in Ruby.
  • Ruby does not allow Booleans in numeric expressions, making expr in 'ohmx'[expr] much more challenging to concoct than in Python.
  • Surprisingly, and unlike Python, Ruby returns the integer ord value if you use 'ohmx'[expr], forcing you to essay the longer 'ohmx'[expr,1] or '%c'%'ohmx'[expr] to get at the character.
  • Perl and PHP, unlike Python and Ruby, do not allow you to address individual characters in a string via the clear and concise 'ohmx'[expr] notation. Though Perl does provide substr for this purpose, that is usually too long for golf. I'd love to see Perl support the 'ohmx'[expr] notation for strings.
  • Oddly, PHP allows $r='ohmx';$r[expr] while not allowing 'ohmx'[expr]. Hideous.
  • All four languages allow you to index strings and arrays from their end via negative indices. This proved handy here in Python in that [-(i==m)^(i==h)] is one stroke less than the pedestrian [2*(i==m)+(i==h)]: the latter generates indices of 0,1,2,3; the former 0,1,-2,-1.
  • In PHP, '}' above can be shortened by one stroke by exploiting the string bitwise negation operator, as described in the next section.

Python Update: Much later I learnt that hallvabo applied his favourite slice and dice technique in this game. That is, instead of:

'ohxm'[-(i==m)^(i==h)]
he employed:
'ohxm'[i==m::2][i==h]
Though these two different methods produce a similar golf score, the former has greater potential, especially if you can unearth a magic formula that does not require parentheses.

Quoted Strings

I know it's weird, but it does make it easier to write poetry in perl.

-- Larry Wall "explains" why sort X is syntactically valid on comp.lang.perl 21 April 1990

But also barewords were added so that Sharon could write better poetry. Hence, it is also called "poetry mode" in some of my earlier writings.

-- oldbie merlyn answers a packrats mailing list question

Perl is perhaps the only computer language in history where the ability to compose poems affected its design. Without use strict, Perl is poetry for golfers because barewords, sans quotation marks, are two strokes shorter than equivalent quoted strings. How on earth did barewords come about? Well, when Larry was designing Perl at JPL in the early 1990s, his concentration was frequently interrupted by poetry readings in the next cubicle. His cubicle buddy from those early days, you see, was gifted poet Sharon Hopkins. And Sharon liked writing poems in Perl. This most improbable chance circumstance affected the early design of Perl, with barewords a feature added primarily to allow Sharon to write better Perl poems. Incidentally, Sharon remains a close Wall family friend today -- though I have no information on whether Perl 6 will similarly feature a Sharon-inspired poetry mode.

listen (please, please);
open yourself, wide,
join (you, me),
connect (us,together),
tell me.

-- Sharon Hopkins, from her classic Perl poem "listen"

A Perl bareword starts with an "alphabetic" [A-Za-z_] character and is followed by zero or more [A-Za-z_0-9] characters. This is an oversimplification though, in that some lowercase characters, such as m and s, require quoting to disambiguate them from Perl operators (such as pattern match m and substitution s). All other characters, in particular those in the ord range 127-255, require quoting in Perl. But not in PHP, as we shall see.

Oh, freddled gruntbuggly thy micturations are to me
As plurdled gabbleblochits on a lurgid bee.
Groop, I implore thee, my foonting turlingdromes.
And hooptiously drangle me with crinkly bindlewurdles
Or else I shall rend thee in the gobberwarts with my blurglecruncheon, see if I don't!

-- Arthur Dent and Ford Prefect being tortured by Vogon poetry (by Prostetnic Vogon Jeltz) in Hitchhiker's Guide to the Galaxy

Like Ford and Arthur, I'm sure I'd find listening to PHP poetry agonizing. That said, I'm (thankfully) unaware of any sort of PHP poetry community, and so have no idea why barewords are similarly allowed in PHP. Perhaps for Perl compatibility. Curiously, PHP allows many more barewords than Perl because characters in the ord range 127-255 are deemed "alphabetic" by PHP. Whether this was a deliberate design decision or an accident of implementation, I don't know, but golfers are fond of it because combining this eccentric "feature" with PHP's Perl-like string bitwise ~ operator allows you to save at least one stroke with just about any string! In this game, for instance, the string '%' requires three strokes in the other three languages. Not so in PHP. Given that ord(~'%') evaluates to 218 (i.e. 255-ord('%')), you can write the three stroke '%' string as a two stroker, namely ~X, where X is the character with ord value 218.

Python is the BOM

Python and Ruby always require all strings to be quoted. Hardly poetic, but more sensible IMHO. Ruby provides no further restrictions that I'm aware of. Python, on the other hand, is annoyingly picky. The characters with ord values 0 (NULL), 10 (LF) and 13 (CR) cannot be placed inside quoted strings; you must use the two stroke escapes \0, \n and \r instead. Worse, characters in the ord range 128-255 cannot be placed inside Python quoted strings at all unless you start the file with a three character UTF-8 BOM, namely chr(0xef).chr(0xbb).chr(0xbf). That is, you have to take a three stroke penalty drop in Python to use strings in the 128-255 ord range; this proved crucial in this game, as will be seen later.

String Interpolation

Consider how to create a "Dear John" string in each of the four languages:

"Dear $name" # Perl and PHP "Dear %s" % expr # Python and Ruby % printf-like operator "Dear {0}".format(expr) # Python format string method "Dear "+`expr` # Python backticks (TMTOWTDI) "Dear #{expr}" # Ruby string interpolation "Dear @{[expr]}" # Perl "Baby Cart" string interpolation "Dear {expr}" # Perl 6 version of Baby Cart (I think)

As discussed last time, my 102 stroke Perl solution creates a printf format string on the fly:

printf"%@{[.1*vec'XXXXXXXXXXXX',$_,8]}s",($_^$`%12?g:p)&($_^$'/5?g:u)| +"H "for map{$_,11-$_}<>!~/:/..5
What's the shortest way to create this peculiar printf format string in each of the four languages?

TechniquePerlRubyPythonPHP
String interpolate"%@{[expr]}s""%#{expr}s""%%%ds"%expr~X.expr.s
Number of strokes1311129

where X above is the character with ord value 218. Poor old Baby Cart baby-stepped to the finish line in last place in this oddball race. This is hardly surprising given Baby Cart was not really designed; it was independently "invented" shortly after Perl 5 was released in 1994 by both L.Wall and R.Schwartz ... and later celebrated as one of Perl's secret operators.

String Multiply

Consider how to create a string of five Xs in Python, Ruby, Perl and PHP:

"X" * 5 Python "X" * 5 Ruby "X" x 5 Perl str_repeat("X", 5) PHP
This makes it immediately obvious why string multiply is unlikely to form part of a winning PHP golf solution.

Curiously, Python is the only member of the gang of four languages to allow you to reverse the order of the two string multiply operands:

5 * "X" also produces "XXXXX" in Python! TMTOWTDI! :) 5 * "X" ... but not in Ruby (won't compile: type error) 5 x "X" ... or Perl (produces empty string)
That is, the string multiply operator is commutative in Python, but not in Perl or Ruby. This language idiosyncrasy makes string multiply based solutions most attractive in Python. To illustrate, note these code snippets from my string multiply based solutions to this game:
$"x(318%$_/9) Perl " "*(318%i/9) Ruby 318%i/9*" " Python
This is a very rare example of Python out-golfing both Perl and Ruby.

Game Solutions

Finally, I'll present my shortest solutions in Ruby, Python and PHP. I hope the preceding discussion will make them a bit easier to appreciate.

Python (127 strokes)

The only way I could make any progress in Python was to pursue the magic formula approach -- which also resulted in my shortest Perl solution, as discussed last time.

As a Hitchhiker's Guide to the Galaxy fan, I was more satisfied with my Python solution than any other because it featured the magic number 42 not once, but twice!

r=raw_input() for c in'XXXXXXXXXXXX':i=ord(c);print~60495%i/4*" "+'hxmo'[(i%12==int( +r[3:])/5)^(i-int(r[:2]))%-12/42]+i/42*"\n",
where XXXXXXXXXXXX above is a string with ord values 48, 23, 85, 22, 86, 9, 87, 20, 88, 31, 77, 78. Here's an alternative solution with only one 42 in it:
r=raw_input() for c in'XXXXXXXXXXXX':i=ord(c);print~60495%i/4*" "+'ohxm'[-(i%12==int +(r[3:])/5)^1>>(i-int(r[:2]))%12]+i/42*"\n",

Of note in this solution is the ~60495%i/4 magic formula, three strokes longer than the earlier Perl one of 318%i/9. Why on Earth would I do that? Well, by going longer on the magic formula, my search program revealed that I could constrain the magic string to characters in the 0-127 range and thus avoid Python's dreaded three stroke BOM penalty. Crucial here is that the former magic formula, though three strokes longer, only costs two strokes in practice because of the requirement for a space after print, as shown below:

print~60495%i/4 print 318%i/9

Ruby (112 strokes)

This is essentially just a Ruby translation of my Python solution, without needing to worry about the Python BOM.

r=*$<;'XXXXXXXXXXXX'.each_byte{|i|$><<"%#{327%i/9}c"%'ohmx'[1>>(i-r.to +_i)%12^2&2<<i%12-r[3,2].to_i/5]+$/*(i/85)}
where XXXXXXXXXXXX above is a string with ord values 120, 47, 253, 22, 194, 21, 183, 44, 196, 55, 125, 246.

Using %c looks odd, but turned out to be shorter because you now don't need a trailing ,1 in expr in 'ohmx'[expr].

I really struggled with the expr in 'ohmx'[expr] above because Ruby, unlike Python, does not allow Booleans to be used in numeric expressions.

Generally, my Ruby golf is pretty weak, so I expect someone like flagitious could peer at the solution above, pull a face, and then shorten it with ease.

Number of Leading Spaces

For subtle tactical reasons, the Perl, Python and Ruby string multiply based solutions differ slightly in the number of leading spaces, as shown in the table below.

IndexPerl 318%i/9Python ~60495%i/4Ruby 327%i/9
0889
1445
2768
3112
4131214
5001
6151416
7112
8131214
9445
10768
11889

Notice that the Ruby solution, using "%#{327%i/9}c", requires one more space for each index than Perl's plain string multiply $"x(318%$_/9). The Python solution, on the other hand, requires one less space than the Perl one, but only for indices 2, 4, 6, 8 and 10 (corresponding to clock face marks 1, 2, 3, 4 and 5). This is due to a quirk in Python's print statement when you end it with a comma to suppress the usual newline suffix: if what is printed does not already end in a newline (as in the print statements before clock face marks 1, 2, 3, 4 and 5), the newline suffix is replaced by a space.

PHP (129 strokes)

Lacking a string multiply operator, I had little choice but to squeeze all I could out of printf, constructing a PHP version of my 102 stroke Perl "Baby Cart" solution, discussed earlier.

<?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);
where XXXXXXXXXXXX above is a string with ord values 153, 146, 86, 72, 86, 163, 136, 204, 234, 244, 234, 204, X is a string with ord value 218, and XXX is a string with ord values 183, 245, 245.

Of note in this solution is the exploitation of printf's return value to terminate the loop. Notice that PHP's printf function -- like C, but unlike Perl, Ruby and Python -- returns the number of characters output. For this "loop termination via printf return value" trick to work, the printf return value corresponding to the last clock face mark (i.e. 6) must be distinct from all the other clock face marks. Luckily, for clock face mark 6, we have a choice of three different printf format strings, namely 9.1, 10.2 and 11.3. And we need them, for only one of them, 11.3, is distinct. Like most winning golf solutions, this one requires an element of luck.

I had an enjoyable tussle with ToastyX in this game. He was about 20 strokes ahead at one point, but I "did a Jasper" and just kept on relentlessly whittling until I eventually overtook him. After this protracted duel, ToastyX and I found ourselves 30 strokes clear of the field.

Hanging up the Clubs

Much as I enjoy playing golf, it's taking more time than I can afford, so it's time to hang up the clubs for now.

Sometimes I think I enjoy writing articles about golf more than actually playing. I hope you've enjoyed reading this series of articles as much as I've enjoyed writing them.

References

Updated 14-jan: Added poem by Prostetnic Vogon Jeltz. Added new "Number of Leading Spaces" section.

Comment on The golf course looks great, my swing feels good, I like my chances (Part VI)
Select or Download Code
Re: The golf course looks great, my swing feels good, I like my chances (Part VI)
by steve (Deacon) on Dec 30, 2009 at 16:42 UTC

    ++ I have thoroughly enjoyed each article in this series. The step by step analysis is fascinating, along with the comparison between all four languages.

    Thank you for contributing your knowledge and taking the time to compose these for us.

Re: The golf course looks great, my swing feels good, I like my chances (Part VI)
by eyepopslikeamosquito (Canon) on Jan 10, 2010 at 18:33 UTC

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

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

Re: The golf course looks great, my swing feels good, I like my chances (Part VI)
by Anonymous Monk on Jan 11, 2010 at 02:33 UTC
    Thank you for all of the articles!
    It really helped me, getting into golfing :)

    J-_-L
Re: The golf course looks great, my swing feels good, I like my chances (Part VI)
by eyepopslikeamosquito (Canon) on Feb 28, 2010 at 20:10 UTC

    A few weeks after posting this node, I noticed that Korea's leading golfer, "leonid", had jumped from 118 strokes to 111 strokes, thus snatching the Ruby lead from flagitious and me.

    To respond to this provocation, I began by staring at my 112 stroker:

    r=*$<;'XXXXXXXXXXXX'.each_byte{|i|$><<"%#{327%i/9}c"% 'ohmx'[1>>(i-r.to_i)%12^2&2<<i%12-r[3,2].to_i/5]+$/*(i/85)}
    The most obvious candidate for improvement here is the ungainly expression in 'ohmx'[expr], namely:
    1>>(i-r.to_i)%12^2&2<<i%12-r[3,2].to_i/5
    which can be clarified somewhat by writing as:
    1>>(i-h)%12^2&2<<i%12-m
    where h represents hours (0..23) and m minutes (0..11). How to shorten?

    Though relying on inspiration in cases like these might work on a good day, a more dependable approach is to write a program to search for shorter solutions. Accordingly, I wrote the following Ruby brute force searcher:

    # Search for Ruby solutions by building expressions from the sym # and numsymop arrays below, then evaluating them (via eval). # 0, 11, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6 i_arr = [ 120, 47, 253, 22, 194, 21, 183, 44, 196, 55, 125, 246 ] sym = [ '*', '/', '%', '+', '-', '>>', '<<', '&', '^', '|' ] numsymop = (-12..12).to_a numsymop.unshift('i'); numsymop.unshift('-i'); numsymop.unshift('~i') numsymop.unshift('m'); numsymop.unshift('-m'); numsymop.unshift('~m') for op2 in numsymop for s2 in sym $stderr.print "#{op2} #{s2}\n" for op3 in numsymop for s3 in sym for op4 in numsymop for s4 in sym for op5 in numsymop expr = op2.to_s + s2 + op3.to_s + s3 + op4.to_s + s4 + o +p5.to_s i = 120; m = 0 # (d = 0) c_expr = "i=#{i};m=#{m};" + expr veq1 = -4242 begin veq1 = eval(c_expr) rescue end next if veq1 == -4242 i = 47; m = 11 # (d = 11) c_expr = "i=#{i};m=#{m};" + expr veq2 = -4242 begin veq2 = eval(c_expr) rescue end next if veq2 == -4242 next if veq1 != veq2 i = 120; m = 11 # (d = 0) c_expr = "i=#{i};m=#{m};" + expr vneq1 = -4242 begin vneq1 = eval(c_expr) rescue end next if vneq1 == -4242 next if vneq1 == veq1 i = 47; m = 10 # (d = 11) c_expr = "i=#{i};m=#{m};" + expr vneq2 = -4242 begin vneq2 = eval(c_expr) rescue end next if vneq2 == -4242 next if vneq1 != vneq2 good = 1 for i in i_arr d = i % 12 m = d c_expr = "i=#{i};m=#{m};" + expr veq2 = -4242 begin veq2 = eval(c_expr) rescue end if veq2 == -4242 good = 0 break end if veq1 != veq2 good = 0 break end end next if good == 0 for i in i_arr d = i % 12 for m in 0..11 next if m == d c_expr = "i=#{i};m=#{m};" + expr vneq2 = -4242 begin vneq2 = eval(c_expr) rescue end if vneq2 == -4242 good = 0 break end if vneq1 != vneq2 good = 0 break end end break if good == 0 end next if good == 0 print "woohoo: #{expr}: veq=#{veq1} vneq=#{vneq1}\n" $stdout.flush end end end end end end end
    As an aside, writing "eval" search programs like these seems to be a good way to unearth bugs in dusty corners of a language; at least, I had to remove '**' from the sym array above to workaround various random crashes and hangs of the Ruby interpreter.

    Anyway, after dutifully chugging away for several hours, the above program did find a lucky hit, namely:

    woohoo: ~m%i%12/11: veq=1 vneq=0
    Further simple variations of this search program found new solutions for the "hours" portion of the expression also, such as:
    (i-h)%12/~i
    Combining these two new expressions enabled me to shorten the overall expression as shown below:
    1>>(i-h)%12^2&2<<i%12-m # original (i-h)%12/~i^~m%i%12/11 # new and improved
    and thus match leonid's 111 strokes via:
    r=*$<;'XXXXXXXXXXXX'.each_byte{|i|$><<"%#{327%i/9}c"% 'hxmo'[(i-r.to_i)%12/~i^~r[3,2].to_i/5%i%12/11]+$/*(i/85)}
    Of course, I have no idea if this is the same one stroke improvement that leonid found. If not, and he reads this post, I expect shortly to watch him move ahead to 110 strokes. :)

    My main point is that relying on inspiration alone, I doubt I would ever have thought of this new shortened solution. That is, writing and running little search programs like these is part and parcel of being a serious golfer.

    Update: Some time later, I discovered an easier way to get to 111 by changing the way hours and minutes are extracted from stdin, as shown below:

    r=*$<; r r[3,2] # original gets;~/:/; $_ $' # new
    By way of explanation, note that the Ruby gets function automatically sets $_. And, like Perl, ~/:/ automatically sets $'. Unlike Perl though, ~/:/ returns the (zero-based) index of the match. In this game therefore, ~/:/ always returns two (the index of : in, for example, 12:34).

    At first glance, the new method looks like a step backwards because it consumes one more stroke than the original. As is often the case in golf though, a tactical trick is available to shorten it. By pure luck, you see, the original 2&2<<i%12-m expression contains two twos! So we can replace one of them with the return value of ~/:/ thus transforming the new approach from one stroke longer to one stroke shorter:

    gets;'XXXXXXXXXXXX'.each_byte{|i|$><<"%#{327%i/9}c"%'ohmx'[1>>(i-$_.to +_i)%12^~/:/&2<<i%12-$'.to_i/5]+$/*(i/85)}

    Alas, this new tactical trick cannot be similarly applied to my shortened ~m%i%12/11 expression because, unluckily, it does not contain a two.

    Python Update

    Inspired by this new Ruby magic formula, I was later able to shave a stroke from my 127-stroke Python solution by shortening the last part of the magic formula from:

    (i-int(r[:2]))%-12/42
    to:
    (i-int(r[:2]))%-12/i
    This (cheating) 126-stroke Python "solution" is not correct for all possible inputs but has a good chance of passing the (poor) set of test data provided for this game. To clarify, I chose 42 in the original magic formula for artistic effect only; any number greater than eleven will do. Replacing 42 with i therefore works for eleven of the twelve string ord values, namely 48, 23, 85, 22, 86, 87, 20, 88, 31, 77, 78 ... but fails for the single ord value that is less than eleven, namely the 9 that forms the string's sixth character.

    Hallvabo was kind enough to send me his two best solutions to this game. Here is his (second-placed) 128 stroke Python entry:

    _=raw_input() for c in'XXXXXXXXXXXX':n=ord(c);print 3459%n/9*' '+'ohmx'[n%13==int(_[ +:2])%12::2][n%13==int(_[3:])/5]+n%3*'\n',
    where XXXXXXXXXXXX above is a string with ord values 130, 180, 92, 75, 197, 48, 185, 138, 134, 228, 148, 188. And here is an alternate hallvabo 128-stroker:
    _=raw_input() for c in'XXXXXXXXXXXX':n=ord(c);print 247%n/7*' '+'ohmx'[n%12==int(_[: +2])%12::2][n%12==int(_[3:])/5]+n/72*'\n',
    where XXXXXXXXXXXX above is a string with ord values 72, 71, 205, 34, 158, 9, 147, 20, 160, 43, 101, 186. Note that both these solutions require a leading 3-character BOM (0xef, 0xbb, 0xbf). Note too that hallvabo employed his favourite "slice and dice" trick once again, just as he did in 99 Bottles of Beer.

    It never occurred to me to use "slice and dice" in this game. As it turns out, the two different techniques produce similar lengths, as indicated below:

    [(i%12==int(r[3:])/5)^(i-int(r[:2]))%-12/42] [-(i%12==int(r[3:])/5)^1>>(i-int(r[:2]))%12] [i%12==int(r[3:])/5::2][i%12==int(r[:2])%12] [i%12==int(r[3:])/5::2][1>>(i-int(r[:2]))%12]

      While I'm still able to remember, I list below the other new (albeit unsuccessful) Ruby solutions I found while searching for the elusive 111 stroker. It's possible I've missed something and one of these attempts might be transformed from a loser into a winner.

      The following solutions are all of a similar form to the original 111 stroker, namely:

      r=*$<;'XXXXXXXXXXXX'.each_byte{|i|$><<"%#{327%i/9}c"% 'hxmo'[(i-r.to_i)%12/~i^~r[3,2].to_i/5%i%12/11]+$/*(i/85)}
      where XXXXXXXXXXXX above is a string with ord values 120, 47, 253, 22, 194, 21, 183, 44, 196, 55, 125, 246. For clarity below, I show only the 'hxmo'[expr] expressions:
      'hxmo'[(i-r.to_i)%12/~i^~r[3,2].to_i/5%i%12/11] 'ohmx'[1>>(i-r.to_i)%12^2&2<<i%12-r[3,2].to_i/5] 'hxmo'[(i-r.to_i)%12/-i^0**(i%12^r[3,2].to_i/5)] 'mxho'[1>>(i-r.to_i)%12^(i%12^r[3,2].to_i/5)/-i] 'mxho'[(r[3,2].to_i/5-i)%12/-i^1>>(r.to_i-i)%12] 'hxmo'[(i-r.to_i)%12/-i^1>>(2*r[3]+r[4]/53-i)%12] 'mxho'[(2*r[3]+r[4]/53-i)%12/-i^1>>(i-r.to_i)%12] 'ohmx'[1>>(i-r.to_i)%12^(i%12==r[3,2].to_i/5).id]

      And here are some other random attempts:

      r=*$<;'XXXXXXXXXXXX'.each_byte{|i|printf"%*c"+$/*(i/85), 327%i/9,'hxmo'[(i-r.to_i)%12/~i^~r[3,2].to_i/5%i%12/11]} r=*$<;'XXXXXXXXXXXX'.each_byte{|i|$><<" "*(318%i/9)+ 'hxmo'[(i-r.to_i)%12/~i^~r[3,2].to_i/5%i%12/11,1]+$/*(i/85)} /:/!~gets;'XXXXXXXXXXXX'.each_byte{|i|$><<"%#{327%i/9}c"% 'hxmo'[(i-$_.to_i)%12/~i^~$'.to_i/5%i%12/11]+$/*(i/85)} r=*$<;$><<"%#{327%J/9}c"%'hxmo'[~r[3,2].to_i/5%J%12/11^ (J-r.to_i)%12/~J]+$/*(J/85)while J='ohXXXXXXXXXXXX'[$.+=1] r=*$<;loop{i="hmXXXXXXXXXXXX"[$.+=1];$><<"%#{327%i/9}c"% 'hxmo'[(i-r.to_i)%12/~i^~r[3,2].to_i/5%i%12/11]+$/*(i/85)} r=*$<;'XXXXXXXXXXXX'.each_byte{|i|$><<"%*c"%[327%i/9, (i%12==r.to_i%12??x:?o)&(i%12==r[3,2].to_i/5??}:?o)]+$/*(i/85)} gets=~/:/;'XXXXXXXXXXXX'.each_byte{|i|print" "*(318%i/9), (111+(i%12==$_.to_i%12?9:0)&111+(i%12==$'.to_i/5?14:0)).chr,$/*(i/85)}
      Note that in the solutions above using 318 rather than 327, the XXXXXXXXXXXX string is a string with ord values: 120, 47, 253, 22, 194, 9, 183, 44, 196, 55, 125, 246.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://814900]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (15)
As of 2014-04-17 17:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (453 votes), past polls