- "}" was replaced with v125. Cost: one stroke.
- Hard newline replaced with \n. Cost: one stroke.
- for loop replaced with map. Cost: one stroke.
- <>!~/:/..11 replaced with 0..s//<>/e./:/. Cost: three strokes.
That six stroke investment was returned with interest, the compressed solution being three strokes shorter than the original.# original 96-stroke solution $c[$_*=.52,5.5-4.7*cos][8+7.4*sin]=($_^$`%12?o:x)&($_^$'/5?o:'}')for<> +!~/:/..11;print"@$_ "for@c # 102-stroke pack-friendly makeover map$c[$_*=.52,5.5-4.7*cos][8+7.4*sin]=($`%12^$_?o:x)&($'/5^$_ ?o:v125),0..s//<>/e./:/;print"@$_\n"for@c#```
Well, that seemed pretty easy. So let's apply the same trick to the classic 99 Bottles of Beer.
That chore turned out to be more laborious than expected, and of a completely different character. You see, the shortest (160-stroke) 99 Bottles of Beer solution:
apart from being significantly longer, has an entirely different set of pack-hostile bytes to deal with, namely:@c=(@b=(++$n,bottle.'s'x@-,of,beer),on,the,wall),s//Take one down and +pass it around, @c. @c, @b. /until/, 99\D+/;print$'."Go to the store and buy some more$&"
- Sixteen spaces.
- Three hard newlines.
- Three uppercase characters: T, D and G.
How to deal with the sixteen spaces?
I could see only three possibilities:
- Use $" instead of space; for example, replace Take one with Take$"one. Though perhaps the simplest solution, this comes at a horrendous cost of 16 strokes.
- Use v (say) instead of space; for example, replace Take one with Takevone. Then later translate via y/v/\40/ or s/v/$"/g. This looks more promising, costing only nine strokes: eight for the translation expression, one for a separating comma. The \40 is unfortunate yet I couldn't find anything shorter.
- Use a list and exploit the space automatically inserted between each element when a list is stringified as we already do with @b and @c above. Though this approach has perhaps the greatest potential, there are awkward tactical problems to solve.
How to deal with the three newlines?
You can simply replace a hard newline with \n or $/, at a cost of three strokes, one per hard newline.
Alternatively, if you are already using y/v/\40/ to convert spaces, you can sneak in a newline conversion too via y/v_/\40\n/, say, where underscore is used in place of hard newline. This also has a three-stroke penalty.
How to deal with G, T and D?
G can be replaced with \ug or v71, at a cost of two strokes. Similarly, T can be replaced with \ut or v84. That's all I got. Suggestions welcome.
The /, 99\D+/ regex can be replaced with /,.99[^9]*/ or /,.99.*\n+/ at a cost of two strokes.
Back to the game
I really thought I was done with those damned beer bottles. For good. And then Ruby specialist "dmd" goes and posts a (presumably compressed) 157-stroke Perl solution. Well, I could hardly ignore this provocation. Besides, due to dmd's (presumed) lack of Perl experience, I felt quietly confident that I could overtake him without too much effort. Little did I know.
The two (complementary) strategies I tried in this game are:
- Focus on shortness. Seek out the shortest restricted pack-friendly character set solution you can find. Don't worry about formatting the thing.
- Focus on formatting. Seek out algorithms that fit the required shape like a glove. Don't stress about shortness.
I quickly discovered that I could make no headway with a mechanical translation of the shortest 160-stroke solution to the "pack u" restricted character set. A different approach was required. So I kept trying different bottle golf algorithms until I found approaches that seemed better suited to the restricted character set environment.
At this early stage, the two shortest solutions I had found were 179 strokes, using a list to get rid of the wretched spaces (i.e. approach 3 above):
and 180 strokes, replacing spaces with "v" then translating them later (i.e. approach 2 above):@m=(@z=(++$n,bottle.$&,of,beer),on,the,wall),s/^/,$"@m.\n\n@m,$"@z.\n\ +u@j/until@j=/s/?(take,one,down,an.d,pass,it,around):(go,to,the,store, +an.d,buy,some,more),/^99/m;print$&.$'.$`
Many variations of these two solutions are available.s//\utakevonevdownvandvpassvitvaround,v@s.__/until@s=(@b=++$s.vbottle. +"s"firstname.lastname@example.org,onvthevwall),s//@s,v@b._/,/99/;s/$/\ugovtovthevstorev +andvbuyvsomevmore,v@s./;y/v_/\40\n/;print
Disturbingly, that's nineteen strokes longer than the original 160 stroker. In percentage terms, 160/179 = 0.89 is considerably worse than the 96/102 = 0.94 ratio achieved with Saving Time. And, to rub salt into the wounds, I couldn't make either of these solutions fit snugly into any "pack u" shape.
So I reluctantly switched to strategy two above (i.e. focus on shape-fitting) and finally managed to wrest the lead from "dmd" at 154 strokes by shoehorning this longer 182-stroker:
into this "pack u54" shape:$_=v71.ovtovthevstorevandvbuyvsomevmore;@z=($b=++$n.vbottle."s"x@+.vof +vbeer,onvthevwall),s;^;\utakevonevdownvandvpassvitvaround,v@z.\n\n@z, +v$b.\n;,y/v/\40/until/,.99[^9]*/;print$'.$&
# 1 2 3 4 5 6 7 #234567890123456789012345678901234567890123456789012345678901234567890 +123 v;$_=v71.ovtovthevstorevandvbuyvsomevmore;@z=($b=++$n.vbottle."s"x@+.v +of. vbeer,onvthevwall),s;^;\utakevonevdownvandvpassvitvaround,v@z.\n\n@z,v +$b. ;,y/v/\40/until/,.99[^9]*/;print$'.$& #234567890123456789012345678901234567 # 1 2 3
The extra length of this solution is more than compensated for by the perfect fit for the "pack u54" shape, notably the s;;; substitution near the end:
This is a miraculous fit: two strokes are saved by replacing \n with pack's hard newline and another is shaved by hijacking pack's ";" length byte for use as the s;;; substitution delimiter.s;^;\utakevonevdownvandvpassvitvaround,v@z.\n\n@z,v$b. ;
After punching the air with my fist, I hastily generated and submitted a 154-stroke solution by running this little program:
my $source = <<'PERSEVEROUS'; v;$_=v71.ovtovthevstorevandvbuyvsomevmore;@z=($b=++$n.vbottle."s"x@+.v +of. vbeer,onvthevwall),s;^;\utakevonevdownvandvpassvitvaround,v@z.\n\n@z,v +$b. ;,y/v/\40/until/,.99[^9]*/;print$'.$& PERSEVEROUS my $out = unpack 'u54', uc($source); open my $fh, '>', 'b.pl' or die "error: open b.pl: $!"; binmode $fh; print $fh "eval lc pack u54,'" . $out . "'";
Three strokes ahead now. Yay! That should put Ruby upstart "dmd" in his place! Or so I thought. Within a few days however, the tenacious "dmd" struck back by posting a 151 stroke Perl solution. Drat. Back to the drawing board.
- Compression in Golf: Part I
- Compression in Golf: Part III
- Terje/mtv pdf book of Perl Golf
- The golf course looks great, my swing feels good, I like my chances (Part V)
- Drunk on golf: 99 Bottles of Beer
Acknowledgement: I'd like to thank mtve and hallvabo for their help in preparing this series.