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.
Technique | Perl | Ruby | Python | PHP |
Reading HH:MM | <>=~/:/ | r=*$< | r=raw_input() | $r=fgets(STDIN) |
Extracting hours | $`%12 | r.to_i%12 | int(r[:2])%12 | $r%12 |
Extracting minutes | $'/5 | r[3,2].to_i/5 | int(r[3:])/5 | "$r[3]$r[4]"/5 |
Number of strokes | 16 | 27 | 38 | 34 |
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
Slimming down Jasper's overweight 34-stroke eyesore was a fascinating
challenge in this game in all four languages.
Technique | Perl | Ruby | Python | PHP |
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 strokes | 25 | 27 | 22 | 25 |
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?
Technique | Perl | Ruby | Python | PHP |
String interpolate | "%@{[expr]}s" | "%#{expr}s" | "%%%ds"%expr | ~X.expr.s |
Number of strokes | 13 | 11 | 12 | 9 |
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.
Index | Perl 318%i/9 | Python ~60495%i/4 | Ruby 327%i/9 |
0 | 8 | 8 | 9 |
1 | 4 | 4 | 5 |
2 | 7 | 6 | 8 |
3 | 1 | 1 | 2 |
4 | 13 | 12 | 14 |
5 | 0 | 0 | 1 |
6 | 15 | 14 | 16 |
7 | 1 | 1 | 2 |
8 | 13 | 12 | 14 |
9 | 4 | 4 | 5 |
10 | 7 | 6 | 8 |
11 | 8 | 8 | 9 |
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
- The golf course looks great, my swing feels good, I like my chances (Part I)
- The golf course looks great, my swing feels good, I like my chances (Part II)
- The golf course looks great, my swing feels good, I like my chances (Part III)
- The golf course looks great, my swing feels good, I like my chances (Part IV)
- The golf course looks great, my swing feels good, I like my chances (Part V)
- When One Golfer Speculates On What Another Is Doing...
- Spending Time on Saving Time [golf]
- The Lighter Side of Perl Culture (Part V): Poetry
- Drunk on golf: 99 Bottles of Beer
- Golf competitions in Perl, Ruby, Python or PHP
- Golf competitions in PHP
- Golf competitions in Vim
- jakevoytko Python Saving Time blog
- Abdulla Arif PHP Saving Time blog
- Drew Stephens Perl Saving Time blog
- Tobias Lofgren Perl Saving Time blog
- stackexchange saving time question
- Code Golfing Tips
- How to become a golfing god like Mark Byers in seven steps
- General tips for golfing in Python
References Added Later
Updated 14-jan: Added poem by Prostetnic Vogon Jeltz. Added new "Number of Leading Spaces" section.
Re: The golf course looks great, my swing feels good, I like my chances (Part VI)
by eyepopslikeamosquito (Archbishop) 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";
}
| [reply] [d/l] [select] |
|
| [reply] |
Re: The golf course looks great, my swing feels good, I like my chances (Part VI)
by eyepopslikeamosquito (Archbishop) 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]
| [reply] [d/l] [select] |
|
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.
| [reply] [d/l] [select] |
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.
| [reply] |
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 | [reply] |
|
|