Begin forwarded message: > From: "Luis Parravicini" <lparravi / gmail.com> > Date: September 15, 2007 8:11:03 AM CDT > To: submission / rubyquiz.com > Subject: Please Forward: Ruby Quiz Submission > > I'm sending my solution to the ip-to-country Ruby Quiz. I've done two > scripts, one to read IpToCountry.csv and create another file for quick > access and another to search for the country the ip is in. > > The script to search which country a ip is in (ip.rb), uses a binary > file created by the second script (pre-ip.rb) and performs a binary > search on it. > ---------------------------------------------------------------------- > ------------------------ > require 'readbytes' > > # reads the ip range for the record at position pos > def read_ips(f, pos) > f.pos = pos * 10 > buf = f.readbytes(8) > [ buf[0, 4], buf[4, 8] ].map { |b| b.unpack('N')[0] } > end > > # gets the country for the record at position pos > def get_country(f, pos) > f.pos = pos * 10 + 8 > f.readbytes(2) > end > > # binary search of the ip (based on the binary search at > # http://eigenclass.org/hiki.rb?simple+full+text+search+engine#l20 ) > def binary_search(f, ip, from, to) > while from < to > middle = (from + to) / 2 > pivot = read_ips(f, middle) > > if ip < pivot[0] > to = middle > next > elsif ip > pivot[1] > from = middle+1 > next > end > > if ip >= pivot[0] && ip <= pivot[1] > return get_country(f, middle) > else > return nil > end > end > end > > # converts the ip in a.b.c.d form to network order > def to_network(ip) > aux = ip.split('.').map { |x| x.to_i } > > aux[3] + (aux[2] << 8) + (aux[1] << 16) + (aux[0] << 24) > end > > > ip = ARGV[0] > if ip.nil? > puts "usage: #{__FILE__} ip_address" > exit 1 > end > ip = to_network(ip) > > File.open('ip_country', 'r') do |f| > f.seek(0, IO::SEEK_END) > records = f.tell / 10 > > country = binary_search(f, ip, 0, records) > country = 'not found' if country.nil? > puts country > end > ---------------------------------------------------------------------- > ------------------------ > > This is the script which needs to be run just once before ip.rb can be > used. It reads IpToCountry.csv and creates a binary file of records of > 10 bytes each. Each record has an ip range (from, to) and the country > that range belongs to. > ---------------------------------------------------------------------- > ------------------------ > File.open('ip_country', 'w') do |out| > File.open('IpToCountry.csv', 'r') do |csv| > while line = csv.gets do > next unless line =~ /^"(\d+)","(\d+)"(?:,"[^"]+"){2},"([A-Z]+)"/ > > out.write([$1.to_i].pack('N')) # from > out.write([$2.to_i].pack('N')) # to > out.write($3[0,2]) # country > end > end > end > ---------------------------------------------------------------------- > ------------------------ > > And the times (on a 1.5Ghz Pentium M) are: > > $ time ruby ip.rb 190.16.89.91 > AR > > real 0m0.017s > user 0m0.010s > sys 0m0.000s > > > The time it takes pre-ip.rb to create the file ip_country: > > $ time ruby pre-ip.rb > > real 0m2.810s > user 0m2.590s > sys 0m0.020s > > > -- > Luis Parravicini > http://ktulu.com.ar/blog/