IPアドレスがどこの国に割り当てられたものかを調べる

昨日の延長上の話。ある IP アドレスが、どのサブネットに属しているかがわかれば、あとはそのサブネットがどの国に割り当てられたものかがわかればいい。

cidr.txt

何はともあれ、IP アドレスの国別割り当て状況がわからないと話にならない。でも、世の中良くしたもんで、データを公開してくれている人がいた。ありがたい。

 cf. 世界の国別 IPv4 アドレス割り当てリスト

このページから、cidr.txt をダウンロードした。
CIDR ってのは Classless Inter-Domain Routing の略で、サブネットマスクを使って IP アドレスの割り当てとルーティングをする仕組みのこと。また、例えば 192.168.1.0/24 というサブネットの表記を CIDR表記というようだ。簡単な説明はここ。

 cf. CIDRとは – IT用語辞典 e-Words

cidr.txt は国名コードと割り当てられているサブネットの CIDR表記の一覧になっている。冒頭の10行はこうなっている。

AD 85.94.160.0/19
AD 91.187.64.0/19
AD 109.111.96.0/19
AD 185.4.52.0/22
AD 194.158.64.0/19
AE 2.48.0.0/14
AE 5.30.0.0/15
AE 5.32.0.0/17
AE 5.38.0.0/17
AE 5.53.96.0/21

国名コードと CIDR表記のサブネットはタブ文字で区切られている。
ちなみに AD はアンドラ、AE はアラブ首長国連邦だそうだ。

Ruby版

cidr.txt を読み込んでハッシュに保存しておき、入力された IP アドレスが属するサブネットを探して、見つかったらその国名コードを出力する。cidr.txt が実行ディレクトリにおいてあるのを前提としている。

# coding: utf-8

def main
  cidr = load_cidr("cidr.txt")
  addr = ARGV.shift
  cidr.each do |subnet, country|
    if in_subnet?(addr, subnet)
      puts country
      break
    end
  end
end

def load_cidr(file)
  cidr = {}
  File.open(file) do |f|
    f.each do |line|
      co, subnet = line.chomp.split("\t")
      cidr[subnet] = co
    end
  end
  cidr
end

def ip_to_b(addr)
  addr.split(".").map{|x| sprintf("%08d", x.to_i.to_s(2).to_i)}.join("")
end

def in_subnet?(addr, subnet)
  addr_b = ip_to_b(addr)
  subnet_addr, mask = subnet.split("/")
  subnet_addr_b = ip_to_b(subnet_addr)
  mask = mask.to_i
  addr_b.slice(0...mask) == subnet_addr_b.slice(0...mask)
end


main

IP アドレスの属するサブネットの検索が力技だけど、まあいいか。
実行結果。

takatoh@nightschool $ ruby -rresolv -e 'puts Resolv.getaddress("blog.panicblanket.com")'
210.224.185.170
takatoh@nightschool $ ruby addr_to_co.rb 210.224.185.170
JP

というわけで、このブログの IP アドレスは日本のものだってことが分かった。

Python版

同じやり方。

# coding: utf-8

import sys

def main():
    cidr = load_cidr("cidr.txt")
    addr = sys.argv[1]
    for subnet, co in cidr.items():
        if is_in_subnet(addr, subnet):
            print co
            break

def load_cidr(file):
    cidr = {}
    f = open(file)
    for line in f:
        co, subnet = line.rstrip().split("\t")
        cidr[subnet] = co
    return cidr

def ip_to_b(addr):
    return "".join(map(lambda x: "%08d" % int(bin(int(x))[2:]), addr.split(".")))

def is_in_subnet(addr, subnet):
    addr_b = ip_to_b(addr)
    subnet_addr, mask = subnet.split("/")
    subnet_addr_b = ip_to_b(subnet_addr)
    mask = int(mask)
    return addr_b[:mask] == subnet_addr_b[:mask]


main()
takatoh@nightschool $ python addr_to_co.py 210.224.185.170
JP

IPアドレスがサブネットに含まれるかどうかを判定する

Ruby版

# coding: utf-8

def ip_to_b(addr)
  addr.split(".").map{|x| sprintf("%08d", x.to_i.to_s(2).to_i)}.join("")
end

def in_subnet?(addr, subnet)
  addr_b = ip_to_b(addr)
  subnet_addr, mask = subnet.split("/")
  subnet_addr_b = ip_to_b(subnet_addr)
  mask = mask.to_i
  addr_b.slice(0...mask) == subnet_addr_b.slice(0...mask)
end

addr = "192.168.1.5"
subnet = "192.168.1.0/24"

puts in_subnet?(addr, subnet)

addr2 = "127.0.0.1"

puts in_subnet?(addr2, subnet)
takatoh@nightschool $ ruby is_in_subnet.rb
true
false

ちょっとトリッキーなのは、ip_to_b メソッドの sprintf のところ。頭に 0 をつけて8桁にするために、2進数(の文字列)にしたものを1と0だけからなる10進数だとみなして、%08d というフォーマットに変換している。これで IPアドレスの . で区切られた4つの部分のそれぞれが8桁(言い換えると8ビット)になる。
結果は上に示したとおり。192.18.1.5 はサブネット 192.168.1.0/24 に含まれるけど、127.0.0.1 は含まれない。

Python版

やってることは Ruby 版と同じ。

# coding: utf-8

def ip_to_b(addr):
    return "".join(map(lambda x: "%08d" % int(bin(int(x))[2:]), addr.split(".")))

def is_in_subnet(addr, subnet):
    addr_b = ip_to_b(addr)
    subnet_addr, mask = subnet.split("/")
    subnet_addr_b = ip_to_b(subnet_addr)
    mask = int(mask)
    return addr_b[:mask] == subnet_addr_b[:mask]

addr = "192.168.1.5"
subnet = "192.168.1.0/25"

print is_in_subnet(addr, subnet)

addr2 = "127.0.0.1"

print is_in_subnet(addr2, subnet)

当然結果も同じ。

takatoh@nightschool $ python is_in_subnet.py
True
False

10進数の数値から2進数表現の文字列に変換する

Ruby では Fixnum#to_s メソッドに 2 を引数として与えると、2進数表現の文字列になる。
こんな機能あったんだ、知らなかった。

2.1.1 :001 > 5.to_s
 => "5" 
2.1.1 :002 > 5.to_s(2)
 => "101"

引数は 2 じゃなくてもいい。整数 n を与えると n を基数とした数値表現になる。

2.1.1 :003 > 5.to_s(10)
 => "5" 
2.1.1 :004 > 15.to_s(16)
 => "f" 
2.1.1 :005 > 36.to_s(36)
 => "10" 
2.1.1 :006 > 36.to_s(37)
ArgumentError: invalid radix 37
	from (irb):6:in `to_s'
	from (irb):6
	from /home/takatoh/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'

基数36までは OK で、37になるとダメみたいだ。

一方、Python では2進数に変換する bin 関数がある。

>>> bin(5)
'0b101'

頭に 0b がついてしまうので、とってやる。

>>> bin(5)[2:]
'101'

ちなみに、8進数にするには oct、16進数にするには hex 関数がある。

>>> oct(15)
'017'
>>> hex(15)
'0xf'

Pythonでリストのtranspose

Python には Ruby の Array#transpose に当たるメソッドがない。
で、最初こう書いた。

def transpose(list_of_lists):
    ary = []
    for i in range(len(list_of_lists[0])):
        ary.append(map(lambda x: x[i], list_of_lists))
    return ary

でも、これって内包表記でできそうだよな。と思い直してちょっと悩んで書いたのがこれ。

def transpose(list_of_list):
    return [[x[i] for x in list_of_list] for i in range(len(list_of_list[0]))]

最初のより短くなったけど、二重の内包表記はわかりにくい。もうちょっとわかりやすくはいかないもんだろうか。で、ググってみたら zip と引数展開を使うやり方を見つけた。

 cf. Transpose a matrix in Python – stackoverflow

def transpose(list_of_list):
    return map(list, zip(*list_of_list))

これは分かりやすい。zip の引数に * をつけて渡すことで、引数を展開している。で、このままだとタプルのリストになるので、map を使ってリストのリストに直してるわけだ。

>>> l = [[1,2,3], [4,5,6], [7,8,9]]
>>> zip(*l)
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
>>> map(list, zip(*l))
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

PythonでRubyのArray#each_sliceみたいなもの

Ruby の配列(Array)には each_slice というメソッドがある。配列のスライスを順に返してくれるイテレータだ。こんなふう。

irb(main):001:0> [0,1,2,3,4,5].each_slice(2){|x| p x}
[0, 1]
[2, 3]
[4, 5]
=> nil

昨日、CodeEval の問題を解くのに、この Array#each_slice が使えると思っていつもの Python じゃなくて Ruby で解いたんだけど、Python には Ruby の Array#each_slice に相当するものがないみたい。

で、今日になってジェネレータを使えばいいんじゃないかと気が付いたので、書いてみた。

def each_slice(lis, n):
    s = 0
    while s < len(lis):
        yield lis[s:s+n]
        s += n

for x in each_slice([0,1,2,3,4,5], 2):
    print x
^o^ >python each_slice.py
[0, 1]
[2, 3]
[4, 5]

上の例では、要素数がちょうど割り切れる数だったけど、半端が出る場合はどうだろう。Ruby だとこうなる。

irb(main):002:0> [0,1,2,3,4,5,6].each_slice(2){|x| p x}
[0, 1]
[2, 3]
[4, 5]
[6]
=> nil

Python で書いたスクリプトだと:

^o^ > python each_slice.py
[0, 1]
[2, 3]
[4, 5]
[6]

IndexError になるかと思ったけど、期待通りに動いた。

コマンドライン引数のワイルドカードを展開する

今日も Python ネタ。

とかいいつつ Ruby の話から入るけど、Ruby はコマンドラインのワイルドカードを展開してくれる。

^o^ > type argv.rb
ARGV.each {|a| puts a}

^o^ > ruby argv.rb *.py
argv.py
binary_search.py
bubble_sort.py
counting_sort.py
count_char.py
count_files.py
csv_read.py
csv_write.py
default_arg.py
dict.py
dictionary.py
dict_keys.py
difference.py
difference2.py
enumerate.py
exception1.py
exception2.py
exception3.py
expand_num.py
(以下略)

でも、Python はそんな気の利いたことしてくれない。

^o^ > type argv.py
import sys

print sys.argv


^o^ > python argv.py *.py
['argv.py', '*.py']

そこで、glob モジュールの出番になる。

 cf. 10.7. glob — Unix 形式のパス名のパターン展開

import sys
import glob

files = glob.glob(sys.argv[1])
for file in files:
    print file

実行:

^o^ > python argv2.py *.py
argv.py
argv2.py
binary_search.py
bubble_sort.py
counting_sort.py
count_char.py
count_files.py
csv_read.py
csv_write.py
default_arg.py
dict.py
dictionary.py
dict_keys.py
difference.py
difference2.py
enumerate.py
exception1.py
exception2.py
exception3.py
expand_num.py
(以下略)

辞書を値でソートする

Python で辞書をソートしようとすると、

>>> h = {'a' : 200, 'b' : 300, 'c' : 500, 'd' : 100, 'e' : 400}
>>> for k, v in sorted(h):
...    print k, v
...
Traceback (most recent call last):
  File "", line 1, in 
ValueError: need more than 1 value to unpack

エラーになった。これは、sorted(h) がキーしか返さないからだ。

>>> for k in sorted(h):
...     print k
...
a
b
c
d
e

キーと値がほしいときにはこうする。

>>> for k, v in sorted(h.items()):
...     print k, v
...
a 200
b 300
c 500
d 100
e 400

辞書の items メソッドは、キーと値からなるタプルのリストを返してくれる。

>>> h.items()
[('a', 200), ('c', 500), ('b', 300), ('e', 400), ('d', 100)]

で、そのタプルの第1要素でソートされる。つまり辞書のキーでソートされるわけだ。

でも、今日やりたいのは値でソートすること。そのためには、sorted に key 引数を渡してやればいい。具体的にはこうする。

>>> for k, v in sorted(h.items(), key=lambda x: x[1]):
...     print k, v
...
d 100
a 200
b 300
e 400
c 500

key 引数に渡しているのは、タプルの第2要素を返す関数だ。つまりこの関数の返り値でソートされるわけだな。
ちなみに、降順にするには、reverse=True を渡してやる。

>>> for k, v in sorted(h.items(), key=lambda x: x[1], reverse=True):
...     print k, v
...
c 500
e 400
b 300
a 200
d 100

__main__.py

今日は Python。

ディレクトリやzipファイルに、__main__.py という名前のスクリプトを含めておくと、そのディレクトリやzipファイルを Python の引数に指定することで、__main__.py が実行される。

例を示そう。

print "Hello, Python."

これを、hello フォルダの中においておく。で、次のように実行すると:

^o^ > ls hello
__main__.py

^o^ > python hello
Hello, Python.

このとおり、__main__.py が実行された。

zipファイルにした場合も同じように動く。

^o^ > cd hello

^o^ > zip hello.zip __main__.py
  adding: __main__.py (164 bytes security) (stored 0%)

^o^ > python hello.zip
Hello, Python.

こんな機能があったとは。でも、どういうときに使うんだろ。