# Re: IP to Country (#139)

**URL:** <https://www.ruby-forum.com/t/re-ip-to-country-139/115028>\
**Category:** Ruby\
**Created:** [September 19, 2007, 9:24pm UTC](https://www.ruby-forum.com/t/re-ip-to-country-139/115028 "2007-09-19T21:24:05Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![jameskelly](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/3ab097/32.png) [@jameskelly](https://www.ruby-forum.com/u/jameskelly)\
**Post date:** [September 19, 2007, 9:24pm UTC](https://www.ruby-forum.com/t/re-ip-to-country-139/115028/1 "2007-09-19T21:24:05Z")

</div>

I am extremely pleased with my solution this week:

> ruby IPToCountry.rb 121.121.121.121  
> MY  
> 0.000000 0.000000 0.000000 ( 0.000000)

Wrapping the code in 100.times {…}

> ruby IPToCountry.rb 121.121.121.121  
> MY  
> MY  
> …  
> MY  
> MY  
> 0.062000 0.000000 0.062000 ( 0.078000)

Hmmmm…clearly I’m using a supercomputer. 😛

My solution uses no initialization run. Instead, it uses a binary  
search, making good use of IO#seek and IO#pos. It’s a little atypical  
though - it uses different numbers to compare less than and greater  
than, and looking at a position really means looking at the first two  
numbers on the next line.

Here’s the code:

require ‘benchmark’  
puts Benchmark.measure { 100.times {

dot\_dec\_ip = ARGV[0].chomp

dec\_ip = dot\_dec\_ip[0…2].to\_i \<\< 24  
dot\_dec\_ip = dot\_dec\_ip[(dot\_dec\_ip.index(?.)+1)…-1]  
dec\_ip += dot\_dec\_ip[0…2].to\_i \<\< 16  
dec\_ip += dot\_dec\_ip[dot\_dec\_ip.index(?.)+1,3].to\_i \<\< 8  
#Last 8 bits are all in the same country; they don’t matter

dec\_ip = dec\_ip

dataf = File.new(“IPToCountry.csv”)

###Begin binary search, finding high and low

#Hardcoded character offset of where to start. This should be the index  
of  
#a character on the last line of comments

# 

#Earlier versions used 0 or calculated this each iteration.  
#The former yielded bad results (for obvious reasons);  
#the latter doubled the time needed.  
low = 6603

dataf.seek(0,IO::SEEK\_END)  
flen = dataf.pos

high = flen

while true  
if low == high - 1  
puts “IP not assigned”  
break  
end  
mid = (low + high) \>\> 1  
dataf.seek(mid,IO::SEEK\_SET)  
dataf.gets  
dataf.getc  
range\_start = dataf.gets(‘"’)  
range\_start.slice!(-1)  
range\_start = range\_start.to\_i  
cmpno = dec\_ip \<=\> range\_start  
if cmpno == -1  
high = mid  
next  
else  
dataf.read(2)  
range\_end = dataf.gets(‘"’)  
range\_end.slice!(-1)  
range\_end = range\_end.to\_i  
if (dec\_ip \<=\> range\_end) == 1  
low = mid  
next  
else  
puts dataf.gets.match(/“(\w\w)”/)[1]  
break  
end  
end  
end  
}}

----- Original Message ----  
From: Ruby Q. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
To: ruby-talk ML [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Sent: Friday, September 14, 2007 7:31:41 AM  
Subject: [QUIZ] IP to Country (#139)

The three rules of Ruby Q.:

1. Please do not post any solutions or spoiler discussion for this quiz  
until  
48 hours have passed from the time on this message.

2. Support Ruby Q. by submitting ideas as often as you can:

[http://www.rubyquiz.com/](http://www.rubyquiz.com/)

1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps  
everyone  
on Ruby T. follow the discussion. Please reply to the original quiz  
message,  
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

This week’s Ruby Q. is to write a simple utility. Your program should  
accept  
an IP address as a command-line argument and print out the two letter  
code for  
the country that IP is assigned in. You can find a database for the  
matching  
at:

```
http://software77.net/cgi-bin/ip-country/geo-ip.pl

```

To keep the problem interesting though, let’s write our programs with a  
focus on  
speed and memory efficiency.

```
$ time ruby ip_to_country.rb 68.97.89.187
US

real 0m0.314s
user 0m0.259s
sys 0m0.053s

```
