【问题标题】:How can I improve performance of nested comprehension?如何提高嵌套理解的性能?
【发布时间】:2020-01-06 16:41:36
【问题描述】:

我正在尝试使用 python 3.x 理解来创建嵌套字典结构。我的理解语法有效,但速度很慢,尤其是对于大型数据集。我还使用循环创建了我想要的数据结构,它运行得更快,但我想知道是否有一种方法可以提高这种理解,使其更高效,并且可能运行得与我的循环代码一样快,甚至更快。

我的输入数据是一个字典列表,每个字典都概述了业余无线电联系人的详细信息(日志条目)。这是我的数据的一个随机子集(限制为 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 等),值将是一个字典,列出该波段上联系的县作为键,以及该波段上每个国家的联系人计数为价值。这是我的输出的样子:

{'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
}

因为这是一个三重嵌套的理解,并且所有 3 个循环都遍历整个 log_entries 列表,我假设这就是它变得非常慢的原因。

有没有更有效的方法来通过理解来实现这一点?我很好地使用我的循环来处理数据,但我正在努力提高我的理解能力,所以我认为这将是一个很好的练习!

这就是我在不使用理解的情况下所做的事情:我有一个函数 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

这本身可能效率不高,但我的完整代码在构建多个字典的 analyze_log_entry 函数中有许多类似的块。因为我只浏览所有数据一次,并在适当的地方构建字典,所以它可能比使用理解(本质上是多个循环)效率更高。正如我所说,这更像是一个练习,可以学习如何用不同的方法完成相同的任务。

【问题讨论】:

  • 您的 算法 似乎是多项式时间。不使用推导式你在做什么?

标签: python performance dictionary nested list-comprehension


【解决方案1】:

编辑:字典理解版本:

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.groupbycollections.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

【讨论】:

  • 时间呢?
  • 请注意,这不像 OP 要求的那样使用字典理解。此外,对于分组活动,时间复杂度并不理想,这是 O(N log N),可能是 O(N)
  • @juanpa.arrivillaga 我添加了字典理解版本。编辑:我也会添加没有 groupby 的版本。
  • @juanpa.arrivillaga 我将添加不带 groupby 的版本(不是 dict-comprehension,但恕我直言,这种方式更清晰) - 它更快( O(n) 与 O(nlogn) ),请参阅基准。
  • 谢谢。这个答案很有帮助。
猜你喜欢
  • 2021-12-01
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
相关资源
最近更新 更多