Loading...
If the page contents do not appear, it may mean that JavaScript is disabled in your browser. Please enable JavaScript to view this.
An Article from Aaron's Article ArchiveGolf School Ruby Style Photo: Indian Paintbrush and Chain in SandstoneIPv4You are not logged in. Click here to log in. | |
Use Google to search aarongifford.com:
Here is one of my web log entries, perhaps from my Yakkity Yak page, What's New page, or one of my Astounding Adventures from my Geocaching section: Golf School Ruby Style
Sunday, 07 March 2010 12:38 AM MST
Yakkity Yak
I've been schooled while playing code golf with Ruby (or Ruby Golf). This weekend I posted a message to the Ruby Talk mailing list, linking to a little 276-character base-64 encoder written in Ruby I'd had fun writing. (Yes, there are FAR better ways to do base-64 encoding in Ruby—I wrote it purely for the fun of exploring the language.)
When programmers (geeks/nerds) start swapping bits of code that do one little thing, trying to shorten and tighten the code to the smallest size possible, it's called code golf. The goal is to have the lowest score (the smallest code size) that accomplishes the task. So my post to the list opened up base-64 encoding to other code golfers who read the list. Then Stefan responded with his version. Wow! I've been schooled! My amateur status as a code golfer has been exposed! His version taught me a few things about Ruby I was unfamiliar with, things like using ? for character literals (I'd read about that before, but having never used it, promptly forgot it). I was doing this to create an array containing all the characters from A to Z: ('A'..'Z').to_a
Using ? to create character literals in Ruby 1.9, I could have instead done: (?A..?Z).to_a
Net savings: 2 characters. Stefan's code taught me about using the asterisk * character (the splat pseudo-operator) inside of method calls to expand or explode Ruby Range objects (or Enumerators) into full Arrays. I'd used splat before to expand arrays into arguments when calling functions once or twice, but never to expand Ranges that way. Sweet! It's not a true operator, and it only works inside of a method call parameter list, I believe. So (using ? character literals) whereas I could create a Range and use the Range#to_a method to create an array: (?A..?Z).to_a
Stefan taught me I could instead do: [*?A..?Z]
Net savings: 4 characters. My original code created a string: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz++/' It was used as the set of base-64 digits for base-64 encoding. My code created that string like this: (('A'..'Z').to_a|('a'..'z').to_a).join+'0123456789+/'
Stefan shortened that in Ruby 1.9 to: [*?A..?Z,*?a..?z,*?0..?9]*''+'+/'
That's a savings of 20 characters (23 characters over my first/original version that used an extra Array#join call). Notice that instead of using Array#join() he does *'' instead, which is an alternative that's shorter. My algorithm for base-64 encoding took the original data as a String, unpacked each character into an array of integers (Fixnum), then used the Array#inject() method to iterate over each one and generate a base-64 encoded string. I used inject as an alternative to Array#map() so I could have a three-item Array to store stateful information in. I stored the result string, the number of unused bits I still needed to process from the previously processed byte, and the actual unused bits themselves. When inject was finished, it returned the three-item state, which I had to do a final processing step on—handling any as-yet-unprocessed bits and extracting the encoded string, and adding any required padding. Stefan instead took the original input data, unpacked it into a giant string of zeros and ones, binary bits. He then padded it with zeroes at the end until the string length was a multiple of six (since each base-64 digit represents six bits). He then used String#scan with Regexp /^.{6}/ to dice the bit string into an array containing six-character (six-bits each) strings. Then he used Array#map to iterate over each six-bit string, convert it using String#to_i from a base-2 string representation to the actual integer number, and used that number as an index to look up the correct base-64 encoding character. Thus he mapped the string array to an array of base-64 encoded digits. He then used *'' to join the character array into a string, and finally added any needed padding. His methodology switch saved a whopping 104 characters over my inject method. I was delighted that my little bit of fun inspired a pro code golfer to teach me a thing or two. You can follow the email thread in the Ruby Talk archives at: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/358586 I also posted my original version and have since added Stefan's version on my web site: base64.rb Amateur Ruby Golfer, Aaron out. | |
Copyright © 1993-2012 - Aaron D. Gifford - All Rights Reserved |