Как отсортировать IP-адреса, хранящиеся в словаре в Python?
У меня есть фрагмент кода, который выглядит так:
ipCount = defaultdict(int)
for logLine in logLines:
date, serverIp, clientIp = logLine.split(" ")
ipCount[clientIp] += 1
for clientIp, hitCount in sorted(ipCount.items), key=operator.itemgetter(0)):
print(clientIp)
и это своего рода IP-адреса, но вот так:
192.168.102.105
192.168.204.111
192.168.99.11
что не очень хорошо, так как он не признает, что число 99 меньше, чем 102 или 204. Я хотел бы, чтобы результат был таким:
192.168.99.11
192.168.102.105
192.168.204.111
Я нашел этот, но я не уверен, как его реализовать в моем коде, или если это возможно, так как я использую словарь. Какие у меня варианты? Спасибо..
Ответы
Ответ 1
Вы можете использовать пользовательскую функцию key
, чтобы вернуть сортируемое представление ваших строк:
def split_ip(ip):
"""Split a IP address given as string into a 4-tuple of integers."""
return tuple(int(part) for part in ip.split('.'))
def my_key(item):
return split_ip(item[0])
items = sorted(ipCount.items(), key=my_key)
Функция split_ip()
принимает строку IP-адреса типа '192.168.102.105'
и превращает ее в кортеж целых чисел (192, 168, 102, 105)
. Python имеет встроенную поддержку для сортировки кортежей лексикографически.
UPDATE. Это можно сделать еще проще с помощью функции inet_aton()
в модуле socket
:
import socket
items = sorted(ipCount.items(), key=lambda item: socket.inet_aton(item[0]))
Ответ 2
Используйте ключевой параметр сортировки для преобразования вашего ip в целое число, например:
list_of_ips = ['192.168.204.111', '192.168.99.11', '192.168.102.105']
sorted(list_of_ips, key=lambda ip: long(''.join(["%02X" % long(i) for i in ip.split('.')]), 16))
EDIT:
Gryphius предлагает решение с модулем сокета, и поэтому почему бы не использовать его для преобразования из ip в long, поскольку оно чище:
from socket import inet_aton
import struct
list_of_ips = ['192.168.204.111', '192.168.99.11', '192.168.102.105']
sorted(list_of_ips, key=lambda ip: struct.unpack("!L", inet_aton(ip))[0])
Ответ 3
если ваше приложение делает много вещей, таких как "найти ips в диапазоне x", "сортировать по ip" и т.д. часто более удобно хранить числовое значение ip внутри и работать с этим.
from socket import inet_aton,inet_ntoa
import struct
def ip2long(ip):
packed = inet_aton(ip)
lng = struct.unpack("!L", packed)[0]
return lng
преобразуйте число обратно в ip с помощью этой функции:
def long2ip(lng):
packed = struct.pack("!L", lng)
ip=inet_ntoa(packed)
return ip
>>> ip2long('192.168.1.1')
3232235777
>>> ip2long('1.2.3.4')
16909060
>>> long2ip(3232235777)
'192.168.1.1'
>>> long2ip(16909060)
'1.2.3.4'
Ответ 4
Каковы мои варианты здесь?
Два очевидных, которые приходят мне на ум:
- Форматирование строк с IP при их сохранении как из ссылки, указанной в вашем вопросе.
- Передайте функцию сортировки в
sorted()
при выполнении заказа.
Что лучше всего зависит от количества данных, которое вы должны обработать (вы заметите повышенную производительность для метода № 1 только для очень большого объема данных) и от того, что вам нужно будет сделать с указанным отсортированным списком IP (если вы предварительно форматируете строки, вам может понадобиться изменить их снова, прежде чем подавать их в качестве аргументов для других функций, например).
Пример предварительного форматирования
Поддерживать IP как строку, но использует пробелы или нули для решения проблемы с числом разрядов цифр:
>>> ip = '192.168.1.1'
>>> print('%3s.%3s.%3s.%3s' % tuple(ip.split('.')))
192.168. 1. 1
>>> print('%s.%s.%s.%s' % tuple([s.zfill(3) for s in ip.split('.')]))
192.168.001.001
Пример функции сортировки
Ну... Фердинанд Байер в его ответе, похоже, уже предложил отличное решение для этого подхода!:)
Ответ 5
Я думаю, это поможет вам: PEP265 (сортировка со словарями по значению). Просто расширьте отсортированную функцию.
Ответ 6
как насчет того, чтобы вообще не работать со строками и вместо этого конвертировать каждый октет в целое число, а затем передавать его в 4-мерный словарь?
ClientIps[192][168][102][105]=1
ClientIps[192][168][99][11]=1
тогда легко просто отсортировать массив по ключу, не так ли?
for key1, value in sorted(ClientIps.items()):
for key2, value in sorted(ClientIps[key1].items()):
for key3, value in sorted(ClientIps[key1][key2].items()):
for key4, value in sorted(ClientIps[key][key2][key3].items()):
print(key1, key2, key3, key4)
по соображениям скорости может быть также полезно сравнить простой словарь Python с OrderedDict
.
Ответ 7
Чистый способ обработки правильного упорядочения - использование объекта Python "ipaddress". Вы можете преобразовать строки в представления IPv4Address и затем отсортировать их. Вот рабочий пример со списком объектов (протестировано с Python3):
import ipaddress
unsorted_list = [
'192.168.102.105',
'192.168.204.111',
'192.168.99.11'
]
new_list = []
for element in unsorted_list:
new_list.append(ipaddress.ip_address(element))
new_list.sort()
# [IPv4Address('192.168.99.11'), IPv4Address('192.168.102.105'), IPv4Address('192.168.204.111')]
print(new_list)