From: Erik Veenstra Date: 2007-09-18T05:57:15+09:00 Subject: Re: IP to Country (#139) I decided to make one extra, reusable (?), abstraction layer: Class OrderedLinesFile implements method find_line, which is given a block. This block is called with a line of the file. The block has to parse this line and returns -1, 0 or 1 for "go back", "bingo!" and "go forward", respectively. Method find_line then repositions the pointer in the file and calls block again, if necessary. Please shoot... gegroet, Erik V. - http://www.erikveen.dds.nl/ BTW, lines 17899 and 17900 of the downloaded file both contain "2624585728", which is strange... (Downloaded: 2007-09-17 08:34 (UTC).) ---------------------------------------------------------------- $ time ruby quiz139a.rb 11.22.33.44 11.22.33.44 US real 0m0.011s user 0m0.006s sys 0m0.004s ---------------------------------------------------------------- $ wc all_ip_blocks 82358 82358 905115 all_ip_blocks $ time ruby quiz139b.rb all_ip_blocks real 0m45.681s # That's 1800 IPs per second. user 0m40.351s sys 0m5.300s ---------------------------------------------------------------- class OrderedLinesFile def initialize(file_name) @file_name = File.expand_path(file_name) end def find_line(&block) line = nil File.open(@file_name) do |io| position = 0 delta = File.size(@file_name)/2 line = io.gets # The first line of the file. line = io.gets while /^\s*#/ =~ line or /^\s*$/ =~ line while delta > 0 and line and (space_ship = block.call(line)) != 0 position += space_ship < 0 ? -delta : +delta delta /= 2 if position > 0 io.pos = position broken_line = io.gets # Skip the current (broken) line. line = io.gets # The current line of the file. line = io.gets while /^\s*#/ =~ line or /^\s* $/ =~ line else delta = 0 # Stop. end end line = nil if delta == 0 # Nothing found. end line end end class IpToCountry FILE = "IpToCountry.csv" def country_of(ip_addr) ip_addr = ip_addr.split(/\./).collect{|n| n.to_i}.inject{|n, m| n*256+m} # "1.2.3.4" --> 16909060 olf = OrderedLinesFile.new(FILE) res = nil olf.find_line do |line| ip_from, ip_to, registry, assigned, ctry, cntry, country = line.gsub(/"/, "").split(/,/, 7) if ip_addr < ip_from.to_i -1 # Go back in the file. elsif ip_addr > ip_to.to_i +1 # Go forward in the file. else res = ctry 0 # Bingo! end end res end end itc = IpToCountry.new ARGV.each do |ip_addr| puts "%-16s %s" % [ip_addr, itc.country_of(ip_addr)||"??"] end ----------------------------------------------------------------