This is the code I used to generate the quiz example.  To give credit  
where credit is due though, it was heavily inspired from some code  
Allan Odgaard shared with me:

#!/usr/bin/env ruby -wKU

require "open-uri"
require "zlib"

begin
   require "rubygems"
rescue LoadError
   # load without gems
end

begin
   require "faster_csv"
   FCSV.build_csv_interface
rescue LoadError
   require "csv"
end


class IP
   def initialize(address)
     @address_chunks = address.split(".").map { |n| Integer(n) }
     raise AgumentError, "Malformed IP" unless @address_chunks.size == 4
   end

   def to_i
     @address_chunks.inject { |result, chunk| result * 256 + chunk }
   end

   STRING_SIZE = new("255.255.255.255").to_i.to_s.size

   def to_s
     "%#{STRING_SIZE}s" % to_i
   end
end

class IPToCountryDB
   REMOTE = "http://software77.net/cgi-bin/ip-country/geo-ip.pl? 
action=download"
   LOCAL  = "ip_to_country.txt"

   RECORD_SIZE = IP::STRING_SIZE * 2 + 5

   def self.build(path = LOCAL)
     open(path, "w") do |db|
       open(REMOTE) do |url|
         csv = Zlib::GzipReader.new(url)

         last_range = Array.new
         csv.each do |line|
           next if line =~ /\A\s*(?:#|\z)/
           from, to, country = CSV.parse_line(line).values_at(0..1, 4).
                                   map { |f| Integer(f) rescue f }
           if last_range[2] == country and last_range[1] + 1 == from
             last_range[1] = to
           else
             build_line(db, last_range)
             last_range = [from, to, country]
           end
         end
         build_line(db, last_range)
       end
     end
   end

   def self.build_line(db, fields)
     return if fields.empty?
     db.printf("%#{IP::STRING_SIZE}s\t%#{IP::STRING_SIZE}s\t%s\n",  
*fields)
   end
   private_class_method :build_line

   def initialize(path = LOCAL)
     begin
       @db = open(path)
     rescue Errno::ENOENT
       self.class.build(path)
       retry
     end
   end

   def search(address)
     binary_search(IP.new(address).to_i)
   end

   private

   def binary_search(ip, min = 0, max = @db.stat.size / RECORD_SIZE)
     return "Unknown" if min == max

     middle = (min + max) / 2
     @db.seek(RECORD_SIZE * middle, IO::SEEK_SET)

     if @db.read(RECORD_SIZE) =~ /\A *(\d+)\t *(\d+)\t([A-Z]{2})\n\z/
       if    ip < $1.to_i then binary_search(ip, min,        middle)
       elsif ip > $2.to_i then binary_search(ip, middle + 1, max)
       else                    $3
       end
     else
       raise "Malformed database at offset #{RECORD_SIZE * middle}"
     end
   end
end

if __FILE__ == $PROGRAM_NAME
   require "optparse"

   options = {:db => IPToCountryDB::LOCAL, :rebuild => false}

   ARGV.options do |opts|
     opts.banner = "Usage:\n"                                         +
                   "  #{File.basename($PROGRAM_NAME)} [-d PATH] IP\n" +
                   "  #{File.basename($PROGRAM_NAME)} [-d PATH] -r"

     opts.separator ""
     opts.separator "Specific Options:"

     opts.on("-d", "--db PATH", String, "The path to database file")  
do |path|
       options[:db] = path
     end
     opts.on("-r", "--rebuild", "Rebuild the database") do
       options[:rebuild] = true
     end

     opts.separator "Common Options:"

     opts.on("-h", "--help", "Show this message.") do
       puts opts
       exit
     end

     begin
       opts.parse!
       raise "No IP address given" unless options[:rebuild] or  
ARGV.size == 1
     rescue
       puts opts
       exit
     end
   end

   if options[:rebuild]
     IPToCountryDB.build(options[:db])
   else
     puts IPToCountryDB.new(options[:db]).search(ARGV.shift)
   end
end

__END__

James Edward Gray II