Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Comment on

( #3333=superdoc: 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.


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

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • Outside of code tags, you may need to use entities for some characters:
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others perusing the Monastery: (13)
    As of 2014-09-16 15:16 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      How do you remember the number of days in each month?











      Results (33 votes), past polls