【问题标题】:Efficient way to convert a dict into a flat data structure (list or tuple)将字典转换为平面数据结构(列表或元组)的有效方法
【发布时间】:2021-11-17 15:09:24
【问题描述】:

我为棋盘游戏实现了breadth-first search。在这里,我使用dict 来反映每个级别的重复板配置。目前,整体搜索几乎占用了我所有的 RAM (16GB) 用于一次启动配置。我计划为不同的启动配置集成交叉点检查。所以我需要对我找到的配置进行读取访问,并且如果级别完成,一个级别的 dict 不会改变。

这就是为什么我计划在评估下一个级别之前将dict 转换为平面数据结构(listtuple),其中键位于[2n] 位置,值位于[2n+1] 位置。

问题是找到一个快速{1: 2, 3: 4}[1, 2, 3, 4]的转换dict有超过10**8个项目。

我从a comment by Natimanother question 中找到了sum(dict.items(), ()),它有效,但速度太慢(对于超过10**6 个项目的dicts,它似乎停止工作)。

【问题讨论】:

    标签: python list dictionary memory-efficient python-performance


    【解决方案1】:

    在 python 中追加和扩展 list 非常有效:

    def dict_to_list(d):
        li = []
        for k, v in d.items():
            li.extend([k,v])
        return li
    

    因此,就性能而言,上述明显幼稚的函数击败了非常紧凑且以某种方式也优雅的表达式list(sum(d.items(), ()))

    【讨论】:

    • list(chain.from_iterable(a.items()))
    • 这个第一反应并没有作为答案开始,这有点令人遗憾。我认为我很可能会牢记 chain from iterable 的口头禅,而不是使用更多成分的稍快的功能配方。
    【解决方案2】:

    您可以对字典的项目使用列表推导:

    d = {1: 2, 3: 4}
    print([i for t in d.items() for i in t])
    

    这个输出:

    [1, 2, 3, 4]
    

    【讨论】:

    • 速度和“naive function”基本一样
    【解决方案3】:

    使用itertools函数chainclassmethod替代构造函数from_iterable

    >>> from itertools import chain
    >>> list(chain.from_iterable(dct.items()))
    [1, 2, 3, 4]
    >>> 
    

    或者用operator.iconcatfunctools.reduce

    >>> import operator, functools
    >>> functools.reduce(operator.iconcat, dct.items(), [])
    [1, 2, 3, 4]
    >>> 
    

    【讨论】:

    【解决方案4】:

    你可以试试这个:

    dct = {1:2, 3:4, 5:6, 7:8}
    
    out = [None] * 2*len(dct)
    
    for idx, (out[2*idx],out[2*idx+1]) in enumerate(dct.items()):
        pass
    
    print(out)
    

    输出:

    [1, 2, 3, 4, 5, 6, 7, 8]
    

    使用大小为 50_000_000dictionary 检查运行时: (在 colab 上)

    from timeit import timeit
    import operator, functools
    from itertools import chain
    
    def approach1(dct):
        li = []
        for k, v in dct.items():
            li.extend([k,v])
        return li
    
    def approach2(dct):
        out = [None] * 2*len(dct)
        for idx, (out[2*idx],out[2*idx+1]) in enumerate(dct.items()):
            pass
        return (out)
    
    def approach3(dct):
        return functools.reduce(operator.iconcat, dct.items(), [])
    
    def approach4(dct):
        return list(chain.from_iterable(dct.items()))
    
    def approach5(dct):
        return [i for t in dct.items() for i in t]
        
    funcs = approach1, approach2, approach3, approach4, approach5
    dct = {i:i for i in range(50_000_000)}
    
    for _ in range(3):
        for func in funcs:
            t = timeit(lambda: func(dct), number=1)
            print('%.3f s ' % t, func.__name__)
        print()
    

    输出:

    8.825 s  approach1
    13.243 s  approach2
    4.506 s  approach3
    3.809 s  approach4
    7.881 s  approach5
    
    8.391 s  approach1
    13.159 s  approach2
    4.487 s  approach3
    3.854 s  approach4
    7.946 s  approach5
    
    8.391 s  approach1
    13.197 s  approach2
    4.448 s  approach3
    3.681 s  approach4
    7.904 s  approach5
    

    使用不同大小的dictionary检查运行时: (在colab上)

    from timeit import timeit
    import operator, functools
    from itertools import chain
    import pandas as pd
    import seaborn as sns
    import matplotlib.pyplot as plt
    def app_extend(dct):
        li = []
        for k, v in dct.items():
            li.extend([k,v])
        return li
    def app_enumerate(dct):
        out = [None] * 2*len(dct)
        for idx, (out[2*idx],out[2*idx+1]) in enumerate(dct.items()):
            pass
        return (out)
    def app_functools(dct):
        return functools.reduce(operator.iconcat, dct.items(), [])
    def app_chain(dct):
        return list(chain.from_iterable(dct.items()))
    def app_for(dct):
        return [i for t in dct.items() for i in t]
    funcs = app_extend, app_enumerate, app_functools, app_chain, app_for
    dct_rslt = {}
    for dct_size in [100_000, 250_000, 500_000, 1_000_000, 2_500_000, 5_000_000, 10_000_000, 25_000_000, 50_000_000]:
        dct = {i:i for i in range(dct_size)}
        dct_rslt[str(dct_size)] = {func.__name__ : timeit(lambda: func(dct), number=1) for func in funcs}
    df = pd.DataFrame(dct_rslt).T
    fig, ax = plt.subplots()
    fig.set_size_inches(12, 9)
    sns.lineplot(data=df)
    plt.xlabel('Dictionary Size')
    plt.ylabel('Time(sec)')
    plt.show()
    

    【讨论】:

    • ...请不要忘记,对于包含数百万个项目的 dict,它必须很快,也许不容易推断...
    • 我更改了range 中的整数文字以提高可读性。
    • @Wolf 在 colab 上使用运行时编辑了答案,我的电脑很慢 ;)
    猜你喜欢
    • 2018-02-25
    • 1970-01-01
    • 2015-12-22
    • 2011-12-08
    • 1970-01-01
    • 2018-02-03
    • 2012-05-24
    • 2016-06-16
    • 1970-01-01
    相关资源
    最近更新 更多