SATtva Персональная страница
темы архив все rss xml 2.0
 
 
блог
досье
связь
english
 

Циклическая сортировка в Python

05.04.2015
софт

Предположим, у вас есть длинный список ссылок, ведущих на небольшое число хостов. Что-нибудь вроде:

links = ['http://foo.com/page1',
         'http://foo.com/page2',
         'http://foo.com/page3',
         'http://foo.com/',
         'http://blah.org/file0',
         'http://blah.org/file1',
         'http://foobar.net/some_page',
         'http://foobar.net/another_page',
         'http://foobar.net/yet_another',
         'http://blah.org/file2',
         'http://blah.org/file3',
         'http://blah.org/file4']

Надо скачать страницы/файлы по этим ссылкам, но не создавая избыточную нагрузку на каждый из хостов, чтобы, так сказать, пройти ниже радара.* Можно ввести паузы между закачками, но это сильно затянет исполнение.

Самое простое решение -- отсортировать список так, чтобы хосты чередовались между собой, и последовательные запросы гарантировано приходились на разные хосты. Но как выполнить такую сортировку, не прибегая к громоздким циклам? На помощь приходит модуль itertools.

Для начала определим критерий сортировки: нас интересует имя хоста:

from urllib.parse import urlparse

sort_by_host = lambda url: urlparse(url).netloc

Ну, а теперь самое весёлое. Этот код можно легко переписать в однострочник, но, памятуя, что свобода лучше, чем несвобода явное лучше неявного, сделаем его более наглядным:

from itertools import groupby, zip_longest

# Сортируем по именам хостов.
sorted_urls = sorted(links, key=sort_by_host)
# Группируем хосты.
grouped = [list(group) for _, group
           in groupby(sorted_urls, key=sort_by_host)]
# Сортируем хосты циклически.
interleaved = list(zip_longest(*grouped))
# Преобразуем в однородный список без None-элементов.
output = [item for group in interleaved
          for item in group if item is not None]

# Полученный результат.
for link in output:
    print(link)

# http://blah.org/file0
# http://foo.com/page1
# http://foobar.net/some_page
# http://blah.org/file1
# http://foo.com/page2
# http://foobar.net/another_page
# http://blah.org/file2
# http://foo.com/page3
# http://foobar.net/yet_another
# http://blah.org/file3
# http://foo.com/
# http://blah.org/file4

Для ценителей лаконичности та же функция в одну строку. Ну, или уж как получится. :)

output = [i for e in list(zip_longest(*[list(g)
          for _, g in groupby(sorted(links, key=sort_by_host),
          key=sort_by_host)])) for i in e if i is not None]


* Реальная задача, из которой родился пост, была связана с API некоторого сайта, лимитирующим плотность вызовов, но детали той задачи не столь иллюстративны в рамках краткой заметки.
Комментарии
Комментариев нет.
Оставить комментарий
Заголовок:

Текст:

Ваше имя:

Ваш e-mail:


Код подтверждения: