はじめに
IPアドレスやネットワークアドレスを並び替えるときに何も考えずにソートすると、思い通りに並ばないので色々と工夫しないといけないと思います。
例えば192.168.1.1
と8.8.8.8
の2つのIPアドレスを並び替える場合、普通は文字列型のソートになるので、表1のように並んでしまいます。
これを表2のようにPythonでいい感じに並べてみたい。
192.168.1.1 | 8.8.8.8 |
8.8.8.8 | 192.168.1.1 |
2022/10/19 追記:
色々書いてみましたが、結論から言うと、@shiracamusさんからの的確なコメントにあるようにkeyとしてipaddressライブラリのIPv4Addressクラスを利用する手段が簡単です。
#ライブラリを読み込む
#192.168.1.0/24のようなCIDR表記のネットワークアドレスをソートしたい場合はIPv4Addressではなく、ip_network
from ipaddress import IPv4Address, ip_network
#ソートするIPアドレスをリストに格納する
ipaddr_list = ['192.168.1.1', '8.8.8.8']
#IPv4Addressをkeyとして、ソート
ipaddr_list_sorted_ipaddress = [*map(str, sorted(ipaddr_list, key=IPv4Address))]
#ipaddr_list_sorted_ipaddress = [*map(str, sorted(ipaddr_list, key=ip_network))]
print(ipaddr_list_sorted_ipaddress)
結果
['8.8.8.8', '192.168.1.1']
1. sortedのkeyとしてタプルを活用しソートする
IPアドレスをPythonでいい感じにソートできないかなと思い、検索したところ以下のサイトがヒットした。
この回答を参考にして、いい感じにソートしてみる。
・準備
必要なライブラリの読み込み。
#正規表現を扱うライブラリ
import re
データの準備。
#ソートするIPアドレスをリストに格納する(192.168.1.0/24のようなCIDR表記でも可)
ipaddr_list = ['192.168.1.1', '8.8.8.8']
・関数定義
sortedで使用するkeyの関数my_key
を定義する。
keyは以下のようにIPアドレスをオクテットで区切り、タプルを作って、このソート順を利用する。
テキスト | タプル | ||
---|---|---|---|
値 | ソート順 | 値 | ソート順 |
'192.168.1.1' | 小 | (192, 168, 1, 1) | 大 |
'8.8.8.8' | 大 | (8, 8, 8, 8) | 小 |
#文字型のIPアドレスを.と/で分割し、4(ネットワークアドレスの場合は5)つの数値を持つタプルに格納する
def split(ip):
octet_prefix = tuple(int(part) for part in re.split(r'[./]', ip))
return octet_prefix
#keyとしてタプルの順を定義
def my_key(item):
return split(item)
・keyとして関数my_key
を指定して実行
ipaddr_list_sorted = sorted(ipaddr_list, key=my_key)
print(ipaddr_list_sorted)
いい感じじゃないですか?
['8.8.8.8', '192.168.1.1']
・試しにkeyを指定しないで実行
ipaddr_list_sorted_normal = sorted(ipaddr_list)
print(ipaddr_list_sorted_normal)
ですよね
['192.168.1.1', '8.8.8.8']
実戦投入する際はデータに数字
, .
及び/
以外の文字列があるとint関数でValueError
が発生するので、例えば以下のようにIPアドレス、もしくはCIDR表記のネットワークアドレス以外を除外してあげる必要があります。
#IPアドレス, ネットワークアドレスにマッチする正規表現
ptn = r'^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([1-2]?[0-9]|[3][0-2])|)$'
#正規表現にマッチする要素だけを新しいリストに格納する
ipaddr_list_filtered = list(filter(lambda x: re.search(ptn, x), ipaddr_list))
なお、この一見したところ呪文のようなIPアドレス, ネットワークアドレスにマッチする正規表現の意味についてはこちらのこちらの記事を参照してください。
2. PythonでIPアドレスを扱うときに超絶便利なライブラリnetaddrを使ってソートする
もう一つの方法、関数の定義などをしなくても、PythonでIPアドレスを扱うときに超絶便利なライブラリnetaddr
を使えばもう少し簡単にソートできる。
なお、この超便利なライブラリについてはこちらの記事で触れています。
・準備
必要なライブラリのインストールと読み込み。
pip install netaddr
#便利なライブラリのnetaddrを読み込む
from netaddr import IPAddress, IPNetwork
データの準備。
#ソートするIPアドレスをリストに格納する
ipaddr_list = ['192.168.1.1', '8.8.8.8']
・実行する
#netaddrのIPNetworkオブジェクトはそのままでいい感じにソートできるので、keyを使わずにソートして、その後、IPNetworkオブジェクトを文字列に変換する。
#192.168.1.0/24のようなCIDR表記のネットワークアドレスをソートしたい場合はIPAddressではなく、IPNetworkを利用する
#@shiracamusさんに頂いたコメントに沿って修正
#ipaddr_list_sorted_netaddr = list(map(str, sorted(list(map(IPAddress, ipaddr_list)))))
ipaddr_list_sorted_netaddr = list(map(str, sorted(map(IPAddress, ipaddr_list))))
print(ipaddr_list_sorted_netaddr)
やっぱり、いい感じ
['8.8.8.8', '192.168.1.1']
さいごに
このようにIPアドレスやネットワークアドレスをいい感じにソートしてみましたが、これを利用したサイトを立ててますので、よかったらご利用ください。
誰も使ってくれなかったこのサイトは残念ながら閉鎖しました...
以上