PythonでIPアドレスをいい感じにソートするいくつかの方法

<h1>はじめに</h1>

　IPアドレスやネットワークアドレスを並び替えるときに何も考えずにソートすると、思い通りに並ばないので色々と工夫しないといけないと思います。

　例えば<code>192.168.1.1</code>と<code>8.8.8.8</code>の2つのIPアドレスを並び替える場合、普通は文字列型のソートになるので、表1のように並んでしまいます。
　これを表2のようにPythonでいい感じに並べてみたい。

<table>
    <caption>表1. 普通のソート</caption>
    <tr>
        <td>
            192.168.1.1
        </td>
    </tr>
        <td>
            8.8.8.8　　　　　　　
        </td>
    </tr>
</table>

<table>
    <caption>表2. いい感じのソート</caption>
    <tr>
        <td>
            8.8.8.8　　　　　　　
        </td>
    </tr>
        <td>
            192.168.1.1
        </td>
    </tr>
</table>

2022/10/19 追記:
　色々書いてみましたが、結論から言うと、@shiracamusさんからの的確なコメントにあるようにkeyとしてipaddressライブラリのIPv4Addressクラスを利用する手段が簡単です。

```python sort_ipaddr.py

#ライブラリを読み込む
#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']
```

<h1>1. sortedのkeyとしてタプルを活用しソートする</h1>

　IPアドレスをPythonでいい感じにソートできないかなと思い、検索したところ以下のサイトがヒットした。
　この回答を参考にして、いい感じにソートしてみる。

https://stackoverflow.com/questions/6545023/how-to-sort-ip-addresses-stored-in-dictionary-in-python

#### ・準備
　必要なライブラリの読み込み。
```python
#正規表現を扱うライブラリ
import re
```

　データの準備。
```python
#ソートするIPアドレスをリストに格納する(192.168.1.0/24のようなCIDR表記でも可)
ipaddr_list = ['192.168.1.1', '8.8.8.8']
```
#### ・関数定義
　sortedで使用するkeyの関数<code>my_key</code>を定義する。
　keyは以下のようにIPアドレスをオクテットで区切り、タプルを作って、このソート順を利用する。

<table>
    <thead>
        <tr>
            <th colspan = "2">
                テキスト
            </th>
            <th colspan = "2">
                タプル
            </th>
        </tr>
        <tr>
            <th>
                値
            </th>
            <th>
                ソート順
            </th>
            <th>
                値
            </th>
            <th>
                ソート順
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                '192.168.1.1'
            </td>
            <td>
                 小
            </td>
            <td>
                (192, 168, 1, 1)
            </td>
            <td>
                 大
            </td>
        </tr>
        <tr>
            <td>
                '8.8.8.8'
            </td>
            <td>
                 大
            </td>
            <td>
                (8, 8, 8, 8)
            </td>
            <td>
                 小
            </td>
        </tr>
    </tbody>
</table>


```python
#文字型の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として関数<code>my_key</code>を指定して実行
```python
ipaddr_list_sorted = sorted(ipaddr_list, key=my_key)
print(ipaddr_list_sorted)
```

　いい感じ:heart:じゃないですか?
```
['8.8.8.8', '192.168.1.1']
```

#### ・試しにkeyを指定しないで実行
```python
ipaddr_list_sorted_normal = sorted(ipaddr_list)
print(ipaddr_list_sorted_normal)
```

　ですよね:expressionless:
```
['192.168.1.1', '8.8.8.8']
```

　実戦投入する際はデータに<code>数字</code>, <code>.</code>及び<code>/</code>以外の文字列があるとint関数で<code>ValueError</code>が発生するので、例えば以下のようにIPアドレス、もしくはCIDR表記のネットワークアドレス以外を除外してあげる必要があります。

```python
#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アドレス, ネットワークアドレスにマッチする正規表現の意味についてはこちらのこちらの記事を参照してください。

https://qiita.com/aikamanami11/items/aef0b7046d3b44efd681

<h1>2. PythonでIPアドレスを扱うときに超絶便利なライブラリnetaddrを使ってソートする</h1>

　もう一つの方法、関数の定義などをしなくても、PythonでIPアドレスを扱うときに超絶便利なライブラリ<code>netaddr</code>を使えばもう少し簡単にソートできる。
　なお、この超便利なライブラリについてはこちらの記事で触れています。

https://qiita.com/aikamanami11/items/d88183c7ced2df071ea0

#### ・準備
　必要なライブラリのインストールと読み込み。

<pre><code>pip install netaddr
</code></pre>

```python
#便利なライブラリのnetaddrを読み込む
from netaddr import IPAddress, IPNetwork
```
　データの準備。
```python
#ソートするIPアドレスをリストに格納する
ipaddr_list = ['192.168.1.1', '8.8.8.8']
```

#### ・実行する
```python
#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)
```

　やっぱり、いい感じ:heart:
```
['8.8.8.8', '192.168.1.1']
```
<h1>さいごに</h1>

　このようにIPアドレスやネットワークアドレスをいい感じにソートしてみましたが、これを利用したサイトを立ててますので、よかったらご利用ください。

 誰も使ってくれなかったこのサイトは残念ながら閉鎖しました...

以上