Как отсортировать 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)