Let's look at two of my JAPHs and go golfing. Here's some music.
Here's the first one:
@x = (-198,481.830,-268.83,64.690,-5.67,19,137.4,-75.910,18.1,-1.546, -614,1364,-829.6,196.68,-15.92,-612,1425.7,-901.96,221.74,-18.54,-91, 370.75,-236.85,61.75,-5.625);for $a(0..4){for $b(0..4){for $c(0..4){$_+= @x[$c+5*$a]*($b+1)**$c}print chr ; $_ ="Just another Perl hacker," }}
Yeesh. A huge array followed by a triple nested for loop. Let's deconstruct it:
@x = (#numbers... );for $a (0..4) {for $b (0..4) {for $c (0..4) { $_ += @x[$c + 5*$a] * ($b+1)**$c; }print chr ; $_ ="Just another Perl hacker," } }
Now we can start figuring out the loop logic. Using order of operations, $b+1
is first raised to the $c
th power. This result is then multiplied by a particular value in our array of numbers, and added to $_
. This sequence runs five times, with the array index increasing by 1 each time. The result is passed to the second for loop, where it is converted to an ASCII character and printed. $_
is then redefined, but clearly this is a misdirection, because the for loop would cause the new value to be printed no less than 25 times. This line does serve a function though; in a numerical context, a string is equal to 0, so this line is necessary to reset the value of $_
.
After one character is printed, $b
is incremented and the process runs again. After five characters are printed, $a
is incremented and the index shifts by 5.
What do all these loops accomplish? Well, they're actually creating polynomials. Inspired by this post about curve fitting "Hello World," I set about mapping a JAPH curve. I didn't really know what I was getting myself into. Mapping 11 characters is hard enough -- 25 is bordering on insane. So instead, I created five fifth degree polynomials, each mapped to five characters. I used Excel to create the functions, then did some hand-tweaking to reduce the character count (though you'll notice I padded the first line with some trailing zeros). Placing all the parameters into a single array was necessary to bring the JAPH below Usenet's 4 line, 80 character signature limit, as well as providing a challenge. We can optimize it a bit by defining @a=0..4
, and by changing the last statement to $_=$\
.
On to the next one!
@: =('Jowl' ,'anomaly' ,'Oreo' ,'habits' ); @;= (3960,127575,9123,18537);$\=',' ;for (@:){@:[ $.]=(eval '++@:[$.];' x@;[$.]);$.++}"@:"
First we see that two arrays are created: one contains strings, and the other contains numbers. $\
is the output record separator, i.e. what will be appended to every print statement. Inside the for loop, there are two tricks happening: the
plus x
trick, and the string pre-increment trick.
The
trick arose from my frustration that there wasn't a simple "repeat this statement x times" function. The only thing close was x
, which will repeat a string. So I simply changed the statement to a string and repeated it, then evaluated it. Hence the statement here is evaluated @;[$.]
times.
The magic pre-increment is a quirky aspect of Perl. Essentially it allows you to increment characters (not ASCII values). After reaching z, the increment "carries" to the next letter. So in this JAPH, the strings are being incremented to their proper values. Unfortunately the carry system means that it takes a huge number of pre-increments to change a string even slightly (observe that nearly 130,000 increments are needed to change just the last four letters of 'anomaly'). This is why the original strings are so close to their targets.
I revisited this JAPH after brushing up on my golfing technique and now it looks rather embarrassing. Here's the updated version:
@:= (Jowl, anomaly, Oreo, habits);for (0..3) {eval '++@:[$_];' x(3960, 127575,9123,18537)[$_]}"@:," ;
I was surprised to find that quotes weren't required for the array, but I'm not complaining. Changing the output record separator was actually unnecessary; I just tacked on a
to the print statement. I also changed the for loop to use (0..3)
, which eliminates the need for a loop variable. Not sure what I was thinking when I told it to use @:
. I also eliminated the @;
array. These optimizations shave off 27 characters, and the code also executes 0.2 seconds faster on my machine.
Happy golfing!
EDIT: Rearranging the loop allows us to omit the brackets:
@:=(Jowl, anomaly, Oreo, habits);eval '++@:[$_];' x(3960,127575,9123 ,18537)[$_]for (0..3);"@:," ;