Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
# Justin Ethier # September 16, 2007 # # Ruby Quiz 139 - IP Address to Country Code # For more information see http://www.rubyquiz.com/quiz139.htm # Filename = "IpToCountry.csv" # IP to Country Code database file # Utility method to convert an IP (V4) address (as string) to a decimal number # Example from file: # 1.2.3.4 = 4 + (3 * 256) + (2 * 256 * 256) + (1 * 256 * 256 * 256) # is 4 + 768 + 13,1072 + 16,777,216 = 16,909,060 def convert_ip_str_to_num(ip_addr_str) ip_addr = ip_addr_str.split(".") ip_addr_num = ip_addr[0].to_i * 256 ** 3 + ip_addr[1].to_i * 256 ** 2 + ip_addr[2].to_i * 256 ** 1 + ip_addr[3].to_i return ip_addr_num end # Utility method to parse an IP range field of the IP/Country DB file def parse_line(line) line_parsed = line.split(',') # Validate to handle comments and unexpected data if line_parsed != nil and line_parsed.size >= 5 from = line_parsed[0].tr('"', '').to_i to = line_parsed[1].tr('"', '').to_i country_code = line_parsed[4] end return from, to, country_code end # Simple linear search def linear_search(ip_addr_num) IO.foreach(Filename) do |line| from, to, country_code = parse_line(line) if ip_addr_num >= from and ip_addr_num <= to return "Linear Search: #{country_code}" end end return "No Country Found" end # Binary search of data in memory def binary_search(ip_addr_num) fp = File.open(Filename) data = fp.readlines fp.close low = 0 high = data.size while (low <= high) mid = (low + high) / 2 from, to, country_code = parse_line(data[mid]) if (from == nil or from > ip_addr_num) high = mid - 1 elsif (to < ip_addr_num) low = mid + 1 else return "Binary Search: #{country_code}" end end return "No Country Found" end # Binary Seek Search # The fastest method implemented here is to use a binary search to seek through the file # Takes advantage of the fact that all ip ranges are ordered in the file def binary_seek(ip_addr_num) low = 0 high = File.size(Filename) fp = File.open(Filename) # Find first line of real data and set "low" placeholder accordingly line = fp.gets while line.strip.size == 0 or line[0].chr == "#" line = fp.gets low = low + line.size end # Then find the corresponding the IP Range, if any while (low <= high) mid = (low + high) / 2 fp.seek(mid, IO::SEEK_SET) line = fp.gets # read once to find the next line line = fp.gets # read again to get the next full line from, to, country_code = parse_line(line) if (from == nil or from > ip_addr_num) # Safety check high = mid - 1 elsif (to < ip_addr_num) low = mid + 1 else fp.close return "Binary Seek Search: #{country_code}" end end fp.close return "No Country Found" end for ip in ARGV puts binary_seek(convert_ip_str_to_num(ip)) end
This paste will be private.
From the Design Piracy series on my blog: