Как я могу улучшить производительность вложенного понимания? - PullRequest
0 голосов
/ 06 января 2020

Я пытаюсь использовать понимание python 3.x для создания вложенной структуры словаря. Мой синтаксис понимания работает, но он очень медленный, особенно с большим набором данных. Я также создал желаемую структуру данных, используя циклы, и она работает намного быстрее, но я хотел бы знать, есть ли способ улучшить это понимание, чтобы сделать его более эффективным и потенциально работать так же быстро или быстрее, чем мой l oop code.

Мои входные данные представляют собой список словарей, каждый словарь излагает особенности радиолюбительского контакта (запись в журнале). Вот случайное подмножество моих данных (ограничено 20 записями, и ненужные ключи в словаре удалены, чтобы сделать это более понятным)

[{'BAND': '20M',
  'CALL': 'AA9GL',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20170528',
  'TIME_ON': '132100'},
 {'BAND': '20M',
  'CALL': 'KE4BFI',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20150704',
  'TIME_ON': '034600'},
 {'BAND': '20M',
  'CALL': 'W8OTR',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20190119',
  'TIME_ON': '194645'},
 {'BAND': '10M',
  'CALL': 'FY5FY',
  'COUNTRY': 'FRENCH GUIANA',
  'QSO_DATE': '20150328',
  'TIME_ON': '161953'},
 {'BAND': '17M',
  'CALL': 'KD5FOY',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20190121',
  'TIME_ON': '145630'},
 {'BAND': '10M',
  'CALL': 'K5GQ',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20150110',
  'TIME_ON': '195326'},
 {'BAND': '10M',
  'CALL': 'CR5L',
  'COUNTRY': 'PORTUGAL',
  'QSO_DATE': '20151025',
  'TIME_ON': '182351'},
 {'BAND': '20M',
  'CALL': 'AD4TR',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20170325',
  'TIME_ON': '144606'},
 {'BAND': '40M',
  'CALL': 'EA8FJ',
  'COUNTRY': 'CANARY ISLANDS',
  'QSO_DATE': '20170618',
  'TIME_ON': '020300'},
 {'BAND': '10M',
  'CALL': 'PY2DPM',
  'COUNTRY': 'BRAZIL',
  'QSO_DATE': '20150104',
  'TIME_ON': '205900'},
 {'BAND': '17M',
  'CALL': 'MM0HVU',
  'COUNTRY': 'SCOTLAND',
  'QSO_DATE': '20170416',
  'TIME_ON': '130200'},
 {'BAND': '10M',
  'CALL': 'LW3DG',
  'COUNTRY': 'ARGENTINA',
  'QSO_DATE': '20161029',
  'TIME_ON': '210629'},
 {'BAND': '10M',
  'CALL': 'LW3DG',
  'COUNTRY': 'ARGENTINA',
  'QSO_DATE': '20151025',
  'TIME_ON': '210714'},
 {'BAND': '20M',
  'CALL': 'EI7HDB',
  'COUNTRY': 'IRELAND',
  'QSO_DATE': '20170423',
  'TIME_ON': '184000'},
 {'BAND': '20M',
  'CALL': 'KM0NAS',
  'COUNTRY': 'UNITED STATES OF AMERICA',
  'QSO_DATE': '20180102',
  'TIME_ON': '142151'},
 {'BAND': '10M',
  'CALL': 'PY2TKB',
  'COUNTRY': 'BRAZIL',
  'QSO_DATE': '20150328',
  'TIME_ON': '223535'},
 {'BAND': '40M',
  'CALL': 'EB1DJ',
  'COUNTRY': 'SPAIN',
  'QSO_DATE': '20170326',
  'TIME_ON': '232430'},
 {'BAND': '40M',
  'CALL': 'LU6PCK',
  'COUNTRY': 'ARGENTINA',
  'QSO_DATE': '20150615',
  'TIME_ON': '000200'},
 {'BAND': '17M',
  'CALL': 'G3RKF',
  'COUNTRY': 'ENGLAND',
  'QSO_DATE': '20190121',
  'TIME_ON': '144315'},
 {'BAND': '20M',
  'CALL': 'UA1ZKI',
  'COUNTRY': 'EUROPEAN RUSSIA',
  'QSO_DATE': '20170508',
  'TIME_ON': '141400'}]

Я хочу создать словарь, где каждый ключ является полосой (10M, 20M и т. Д. c), а значением будет словарь, в котором в качестве значений будут указаны страны, с которыми контактировали в этом диапазоне, и число контактов для каждой страны в этом диапазоне. Вот как выглядит мой вывод:

{'10M': {'ARGENTINA': 2,
         'BRAZIL': 2,
         'FRENCH GUIANA': 1,
         'PORTUGAL': 1,
         'UNITED STATES OF AMERICA': 1},
 '17M': {'ENGLAND': 1, 'SCOTLAND': 1, 'UNITED STATES OF AMERICA': 1},
 '20M': {'EUROPEAN RUSSIA': 1, 'IRELAND': 1, 'UNITED STATES OF AMERICA': 5},
 '40M': {'ARGENTINA': 1, 'CANARY ISLANDS': 1, 'SPAIN': 1}}

Это понимание, которое я придумал для создания вывода. Он работает, и с ограниченным набором данных, показанным здесь, он работает быстро, но с входным списком из нескольких тысяч записей, он занимает довольно много времени.

worked_dxcc_by_band = {
    z["BAND"]: {
        x["COUNTRY"]: len([y["COUNTRY"]
                           for y in log_entries
                           if y["COUNTRY"] == x["COUNTRY"] and y["BAND"] == z["BAND"]])
        for x in log_entries
        if x["BAND"] == z["BAND"]
    }
    for z in log_entries
}

Поскольку это тройной Это означает, что все три цикла проходят через весь список log_entries, поэтому я предполагаю, что он очень медленный.

Есть ли более эффективный способ выполнить sh с помощью понимания? Я хорошо использую свой l oop для обработки данных, но я пытаюсь улучшить свои навыки в области понимания, поэтому я подумал, что это будет хорошим упражнением!

Это то, что я делаю без использования понимания: я есть функция analyize_log_entry, которую я вызываю при загрузке каждой записи журнала из файла.

from collections import Counter

worked_dxcc_by_band = {}


def analyze_log_entry(entry):
    if "BAND" in entry:
        if "COUNTRY" in entry:
            if entry["BAND"] in worked_dxcc_by_band:
                worked_dxcc_by_band[entry["BAND"]][entry["COUNTRY"]] += 1
            else:
                worked_dxcc_by_band[entry["BAND"]] = Counter()
                worked_dxcc_by_band[entry["BAND"]][entry["COUNTRY"]] = 1

Это само по себе может быть не столь эффективным, но мой полный код содержит много аналогичных блоков в функции analysis_log_entry, которые создают несколько словари. Поскольку я только один раз просматриваю все свои данные и строю словари там, где это уместно, это, вероятно, гораздо более эффективно, чем использование понимания, которое по сути состоит из нескольких циклов. Как я уже сказал, это больше упражнение, чтобы научиться выполнять sh одну и ту же задачу разными методами.

1 Ответ

3 голосов
/ 06 января 2020

РЕДАКТИРОВАТЬ: версия словаря:

out = {band: dict(Counter(v['COUNTRY'] for v in g)) for band, g in groupby(sorted(data, key=lambda k: k['BAND']), lambda k: k['BAND'])}

Вы можете комбинировать itertools.groupby и collections.Counter:

from itertools import groupby
from collections import Counter

s = sorted(data, key=lambda k: k['BAND'])

out = {}
for band, g in groupby(s, lambda k: k['BAND']):
    c = Counter(v['COUNTRY'] for v in g)
    out[band] = dict(c)

from pprint import pprint
pprint(out)

Отпечатки:

{'10M': {'ARGENTINA': 2,
         'BRAZIL': 2,
         'FRENCH GUIANA': 1,
         'PORTUGAL': 1,
         'UNITED STATES OF AMERICA': 1},
 '17M': {'ENGLAND': 1, 'SCOTLAND': 1, 'UNITED STATES OF AMERICA': 1},
 '20M': {'EUROPEAN RUSSIA': 1, 'IRELAND': 1, 'UNITED STATES OF AMERICA': 5},
 '40M': {'ARGENTINA': 1, 'CANARY ISLANDS': 1, 'SPAIN': 1}}

РЕДАКТИРОВАТЬ: без модулей:

out = {}
for i in data:
    out.setdefault(i['BAND'], {}).setdefault(i['COUNTRY'], 0)
    out[i['BAND']][i['COUNTRY']] += 1

from pprint import pprint
pprint(out)

Тест:

from timeit import timeit

from itertools import groupby
from collections import Counter

def sol_orig():
    worked_dxcc_by_band = {z["BAND"]: {x["COUNTRY"] : len([y["COUNTRY"] for y in data if y["COUNTRY"] == x["COUNTRY"] and y["BAND"] == z["BAND"]]) for x in data if x["BAND"] == z["BAND"]} for z in data}
    return worked_dxcc_by_band

def solution():
    out = {band: dict(Counter(v['COUNTRY'] for v in g)) for band, g in groupby(sorted(data, key=lambda k: k['BAND']), lambda k: k['BAND'])}
    return out

def solution_2():
    out = {}
    for i in data:
        out.setdefault(i['BAND'], {}).setdefault(i['COUNTRY'], 0)
        out[i['BAND']][i['COUNTRY']] += 1
    return out

t1 = timeit(lambda: solution(), number=10000)
t2 = timeit(lambda: solution_2(), number=10000)
t3 = timeit(lambda: sol_orig(), number=10000)

print(t1)
print(t2)
print(t3)

Отпечатки:

0.18113317096140236
0.08159565401729196
3.5367472909856588
...