Python如何高效处理复杂的嵌套字典

Posted

技术标签:

【中文标题】Python如何高效处理复杂的嵌套字典【英文标题】:Python how to process complex nested dictionaries efficiently 【发布时间】:2021-12-19 07:33:03 【问题描述】:

我有一个复杂的嵌套字典,结构如下:

example = 
    ('rem', 125): 
        ('emo', 35): 
            ('mon', 133): 
                ('ony', 33): 0
            ,
            ('mor', 62): 
                ('ore', 23): 0
            ,
            ('mot', 35): 
                ('ote', 22): 0
            ,
            ('mos', 29): 
                ('ose', 29): 0
            
        ,
        ('emi', 32): 
            ('min', 109): 
                ('ine', 69): 0
            ,
            ('mit', 58): 
                ('ite', 64): 0,
                ('ity', 45): 0
            
        ,
        ('eme', 26): 
            ('mer', 68): 
                ('ere', 24): 0,
                ('ery', 20): 0
            
        
    ,
    ('iro', 30): 
        ('ron', 27): 
            ('oni', 94): 
                ('nic', 205): 0
            ,
            ('one', 47): 
                ('ned', 86): 0,
                ('ner', 85): 0,
                ('nes', 26): 0,
                ('net', 20): 0
            ,
            ('ona', 44): 
                ('nal', 246): 0
            
        
    ,
    ('det', 122): 
        ('ete', 53): 
            ('ter', 212): 
                ('ery', 72): 0,
                ('era', 35): 0
            ,
            ('ten', 65): 
                ('ene', 21): 0
            
        
    ,
    ('uni', 217): 
        ('nin', 62): 
            ('ine', 32): 
                ('ned', 88): 0,
                ('ner', 74): 0,
                ('net', 25): 0
            
        ,
        ('nim', 31): 
            ('ima', 50): 
                ('mal', 23): 0
            
        ,
        ('niv', 25): 
            ('ive', 28): 
                ('ver', 48): 0
            
        
    ,
    ('nat', 66): 
        ('ati', 21): 
            ('tiv', 517): 
                ('ive', 821): 0
            ,
            ('tin', 230): 
                ('ine', 134): 0,
                ('ina', 22): 0
            ,
            ('tic', 187): 
                ('ice', 23): 0
            ,
            ('tis', 129): 
                ('ise', 25): 0
            ,
            ('tiz', 59): 
                ('ize', 100): 0
            ,
            ('tit', 52): 
                ('ite', 60): 0
            ,
            ('tif', 43): 
                ('ify', 30): 0
            ,
            ('til', 25): 
                ('ile', 79): 0,
                ('ily', 37): 0
            
        
    ,
    ('mar', 286): 
        ('ari', 30): 
            ('rin', 156): 
                ('ine', 168): 0,
                ('ina', 24): 0
            ,
            ('ris', 146): 
                ('ise', 31): 0
            ,
            ('rit', 119): 
                ('ite', 174): 0,
                ('ity', 118): 0
            ,
            ('ril', 63): 
                ('ily', 88): 0
            ,
            ('riz', 56): 
                ('ize', 134): 0
            ,
            ('ric', 30): 
                ('ice', 25): 0
            ,
            ('rid', 25): 
                ('ide', 49): 0
            ,
            ('rif', 24): 
                ('ify', 32): 0
            
        ,
        ('ara', 21): 
            ('rac', 84): 
                ('acy', 60): 0,
                ('ace', 33): 0
            ,
            ('rat', 76): 
                ('ate', 451): 0
            ,
            ('rag', 56): 
                ('age', 90): 0
            ,
            ('rad', 47): 
                ('ade', 36): 0
            
        
    ,
    ('ref', 153): 
        ('efo', 27): dict()
    ,
    ('gen', 150): 
        ('ene', 56): 
            ('nes', 644): 
                ('ese', 33): 0
            ,
            ('ner', 118): 
                ('ery', 37): 0
            
        ,
        ('eni', 25): 
            ('nit', 112): 
                ('ite', 200): 0,
                ('ity', 93): 0
            ,
            ('nin', 59): 
                ('ine', 54): 0
            ,
            ('niz', 33): 
                ('ize', 178): 0
            
        
    ,
    ('pol', 384): 
        ('oly', 255): dict(),
        ('oli', 35): 
            ('lit', 234): 
                ('ity', 698): 0,
                ('ite', 209): 0
            ,
            ('lis', 79): 
                ('ise', 28): 0
            ,
            ('lin', 72): 
                ('ine', 206): 0
            ,
            ('lid', 36): 
                ('ide', 21): 0
            ,
            ('lif', 24): 
                ('ify', 21): 0
            ,
            ('liz', 22): 
                ('ize', 209): 0
            
        ,
        ('ola', 22): 
            ('lat', 165): 
                ('ate', 422): 0
            ,
            ('lar', 48): 
                ('ary', 106): 0
            ,
            ('lan', 27): 
                ('ane', 24): 0
            
        ,
        ('ole', 22): 
            ('len', 60): 
                ('ene', 58): 0
            ,
            ('ler', 39): 
                ('ery', 36): 0
            
        
    ,
    ('lam', 117): 
        ('ame', 33): 
            ('mer', 46): 
                ('ere', 24): 0,
                ('ery', 20): 0
            
        
    ,
    ('mal', 213): 
        ('ala', 64): 
            ('lac', 67): 
                ('ace', 32): 0
            ,
            ('lan', 65): 
                ('ane', 24): 0
            ,
            ('lat', 58): 
                ('ate', 422): 0
            ,
            ('lar', 31): 
                ('ary', 106): 0
            
        ,
        ('ale', 37): 
            ('len', 90): 
                ('ene', 58): 0
            ,
            ('ler', 26): 
                ('ery', 36): 0
            
        
    ,
    ('rev', 156): 
        ('eve', 76): 
            ('ver', 139): 
                ('ery', 27): 0
            ,
            ('vel', 36): 
                ('ely', 168): 0
            
        ,
        ('evi', 44): 
            ('vit', 27): 
                ('ity', 64): 0
            
        ,
        ('evo', 28): dict()
    ,
    ('ura', 42): 
        ('ran', 25): 
            ('ani', 91): 
                ('nic', 76): 0
            
        
    ,
    ('val', 78): 
        ('ale', 28): 
            ('len', 90): 
                ('ene', 58): 0
            ,
            ('ler', 26): 
                ('ery', 36): 0
            
        
    ,
    ('ven', 111): 
        ('ene', 26): 
            ('nes', 644): 
                ('ese', 33): 0
            ,
            ('ner', 118): 
                ('ery', 37): 0
            
        
    ,
    ('lat', 106): 
        ('ati', 39): 
            ('tiv', 517): 
                ('ive', 821): 0
            ,
            ('tin', 230): 
                ('ine', 134): 0,
                ('ina', 22): 0
            ,
            ('tic', 187): 
                ('ice', 23): 0
            ,
            ('tis', 129): 
                ('ise', 25): 0
            ,
            ('tiz', 59): 
                ('ize', 100): 0
            ,
            ('tit', 52): 
                ('ite', 60): 0
            ,
            ('tif', 43): 
                ('ify', 30): 0
            ,
            ('til', 25): 
                ('ile', 79): 0,
                ('ily', 37): 0
            
        ,
        ('ate', 27): 
            ('ter', 153): 
                ('ery', 72): 0,
                ('era', 35): 0
            ,
            ('tel', 117): 
                ('ely', 112): 0
            ,
            ('ten', 74): 
                ('ene', 21): 0
            
        
    ,
    ('aci', 42): 
        ('cid', 21): 
            ('idi', 49): 
                ('dic', 20): 0
            ,
            ('ida', 43): 
                ('dal', 118): 0
            ,
            ('ide', 43): 
                ('der', 40): 0,
                ('des', 38): 0,
                ('ded', 20): 0
            
        
    

它只是复杂对象的一小部分,我发布了这样一个复杂的示例来表示对象的真正复杂程度,完整的对象可以从示例的第一级开始有多达 15 个嵌套级别,并且有更多一级键。

让我解释一下这是什么,这是用于基于马尔可夫链的伪词生成,每个键是一个由三个字母字符串和一个正整数组成的元组,该字符串要么由一个元音字母组成,辅音字母和元音字母或 CVC。

字符串是三个字母的状态,整数代表状态的权重/似然度/频率,后续关卡的每个key是一个可以跟随上一层状态的状态,一个状态只能跟随另一个state 如果另一个状态的最后两个字母是第一个状态的开始,则数字是从前一个状态转换到下一个状态的可能性。

第一级状态是可以在单词开头找到的状态,第二级状态是在单词中作为第二个状态之前的第一级状态之后的状态。 最后一层的值应该是0,表示嵌套结束,在例子中,如果第一层嵌套的嵌套值是0,之后每层嵌套的嵌套值加1,最后嵌套层的嵌套值加3 (第一个状态的长度)应该是六(遍历树生成的单词的长度应该是)。

一个词是通过加权随机选择一个状态产生的,加权随机从它的值中选择一个状态,递归地,直到值为零,然后,取第一个状态的所有字母,从第三个字母开始的字母从第二个状态开始,加入字母。

例如,如果选择是('uni', 'nin', 'ine', 'ned'),则要提取的块是'uni', 'n', 'e', 'd',生成的单词是'unined'

现在我有三个问题,第一个问题是字典没有排序,这个最容易解决。第二个是字典有空的嵌套字典,这些字典意味着分支提前终止,因为分支不能“合法”增长,这些分支是无效的,我想从最后一级到第一级递归地切断无效分支级别(从最后一级到第一级,如果值字典为空,则递归向后弹出键,直到值不再为空),这有点困难。

最困难的部分是,有许多分支只有一个子分支,我需要将这些子分支与它们的父分支递归地合并,从最后一级到第一级向后。

简而言之,我需要它变成这样:


    ('acid', 42): 
        ('idal', 43): 0,
        ('ide', 43): 
            ('ded', 20): 0,
            ('der', 40): 0,
            ('des', 38): 0
        ,
        ('idic', 49): 0
    ,
    ('dete', 122): 
        ('tene', 65): 0,
        ('ter', 212): 
            ('era', 35): 0,
            ('ery', 72): 0
        
    ,
    ('gen', 150): 
        ('ene', 56): 
            ('nery', 118): 0,
            ('nese', 644): 0
        ,
        ('eni', 25): 
            ('nine', 59): 0,
            ('nit', 112): 
                ('ite', 200): 0,
                ('ity', 93): 0
            ,
            ('nize', 33): 0
        
    ,
    ('iron', 30): 
        ('onal', 44): 0,
        ('one', 47): 
            ('ned', 86): 0,
            ('ner', 85): 0,
            ('nes', 26): 0,
            ('net', 20): 0
        ,
        ('onic', 94): 0
    ,
    ('lamer', 117): 
        ('ere', 24): 0,
        ('ery', 20): 0
    ,
    ('lat', 106): 
        ('ate', 27): 
            ('tely', 117): 0,
            ('tene', 74): 0,
            ('ter', 153): 
                ('era', 35): 0,
                ('ery', 72): 0
            
        ,
        ('ati', 39): 
            ('tice', 187): 0,
            ('tify', 43): 0,
            ('til', 25): 
                ('ile', 79): 0,
                ('ily', 37): 0
            ,
            ('tin', 230): 
                ('ina', 22): 0,
                ('ine', 134): 0
            ,
            ('tise', 129): 0,
            ('tite', 52): 0,
            ('tive', 517): 0,
            ('tize', 59): 0
        
    ,
    ('mal', 213): 
        ('ala', 64): 
            ('lace', 67): 0,
            ('lane', 65): 0,
            ('lary', 31): 0,
            ('late', 58): 0
        ,
        ('ale', 37): 
            ('lene', 90): 0,
            ('lery', 26): 0
        
    ,
    ('mar', 286): 
        ('ara', 21): 
            ('rac', 84): 
                ('ace', 33): 0,
                ('acy', 60): 0
            ,
            ('rade', 47): 0,
            ('rage', 56): 0,
            ('rate', 76): 0
        ,
        ('ari', 30): 
            ('rice', 30): 0,
            ('ride', 25): 0,
            ('rify', 24): 0,
            ('rily', 63): 0,
            ('rin', 156): 
                ('ina', 24): 0,
                ('ine', 168): 0
            ,
            ('rise', 146): 0,
            ('rit', 119): 
                ('ite', 174): 0,
                ('ity', 118): 0
            ,
            ('rize', 56): 0
        
    ,
    ('nati', 66): 
        ('tice', 187): 0,
        ('tify', 43): 0,
        ('til', 25): 
            ('ile', 79): 0,
            ('ily', 37): 0
        ,
        ('tin', 230): 
            ('ina', 22): 0,
            ('ine', 134): 0
        ,
        ('tise', 129): 0,
        ('tite', 52): 0,
        ('tive', 517): 0,
        ('tize', 59): 0
    ,
    ('pol', 384): 
        ('ola', 22): 
            ('lane', 27): 0,
            ('lary', 48): 0,
            ('late', 165): 0
        ,
        ('ole', 22): 
            ('lene', 60): 0,
            ('lery', 39): 0
        ,
        ('oli', 35): 
            ('lide', 36): 0,
            ('lify', 24): 0,
            ('line', 72): 0,
            ('lise', 79): 0,
            ('lit', 234): 
                ('ite', 209): 0,
                ('ity', 698): 0
            ,
            ('lize', 22): 0
        
    ,
    ('rem', 125): 
        ('emer', 26): 
            ('ere', 24): 0,
            ('ery', 20): 0
        ,
        ('emi', 32): 
            ('mine', 109): 0,
            ('mit', 58): 
                ('ite', 64): 0,
                ('ity', 45): 0
            
        ,
        ('emo', 35): 
            ('mony', 133): 0,
            ('more', 62): 0,
            ('mose', 29): 0,
            ('mote', 35): 0
        
    ,
    ('rev', 156): 
        ('eve', 76): 
            ('vely', 36): 0,
            ('very', 139): 0
        ,
        ('evity', 44): 0
    ,
    ('uni', 217): 
        ('nimal', 31): 0,
        ('nine', 62): 
            ('ned', 88): 0,
            ('ner', 74): 0,
            ('net', 25): 0
        ,
        ('niver', 25): 0
    ,
    ('uranic', 42): 0,
    ('vale', 78): 
        ('lene', 90): 0,
        ('lery', 26): 0
    ,
    ('vene', 111): 
        ('nery', 118): 0,
        ('nese', 644): 0
    

这就是我的做法:

def merge(obj):
    for k, v in list(obj.items()):
        if isinstance(v, dict):
            merge(v)
            if len(v) == 1:
                a, b = k
                k1, v1 = list(obj.pop(k).items())[0]
                key = (a + k1[0][2:], b)
                obj[key] = v1

def pop_empty(obj):
    for k, v in list(obj.items()):
        if isinstance(v, dict):
            pop_empty(v)
            if not v:
                obj.pop(k)


def nested_sort(dic):
    d = dict()
    for k, v in sorted(dic.items()):
        if isinstance(v, dict):
            d[k] = nested_sort(v)
        else:
            d[k] = v
    return d

pop_empty(example)
merge(example)
nested_sort(example)

我需要处理一个比这复杂得多的极其复杂的对象,有什么更有效的方法来做这些?

对象有很多重复的元素和重复的嵌套字典,一般来说重复很重,记忆是一个巨大的帮助,但是其中两个函数不返回任何值,一个返回一个值,试图用它来包装它lru_cache 将引发unhashable type dict,任何人都可以使用记忆化重新实现相同的想法吗?

【问题讨论】:

【参考方案1】:

好吧,我已经设法从@LeopardShark 的答案中记住了该函数,并使其也修剪了None 值。它的性能要高得多,现在可以处理无法打印的整个复杂对象...

from ctypes import cast, py_object
from functools import lru_cache

@lru_cache(maxsize=None)
def merge_sort(addr):
    result = 
    obj = cast(addr, py_object).value 
    for key, val in sorted(obj.items()):
        if not val and val != 0:
            continue
        if isinstance(val, dict):
            val = merge_sort(id(val))
            if not val:
                continue
            if len(val) == 1:
                k1, val = next(iter(val.items()))
                key = (key[0] + k1[0][2:], key[1])
        result[key] = val
    return result

【讨论】:

【参考方案2】:

使用递归生成器函数的略短的解决方案:

def merge(d):
   for (x, y), b in sorted(d.items()):
      if b and len(b:=dict(merge(b))) == 1:
          [x, y], b = [x+(j:=[*b][0])[0][2:], y], b[j]
      yield ((x, y), b)

print(dict(merge(example)))

输出:

('acid', 42): ('idal', 43): 0, ('ide', 43): ('ded', 20): 0, ('der', 40): 0, ('des', 38): 0, ('idic', 49): 0, ('dete', 122): ('tene', 65): 0, ('ter', 212): ('era', 35): 0, ('ery', 72): 0, ('gen', 150): ('ene', 56): ('nery', 118): 0, ('nese', 644): 0, ('eni', 25): ('nine', 59): 0, ('nit', 112): ('ite', 200): 0, ('ity', 93): 0, ('nize', 33): 0, ('iron', 30): ('onal', 44): 0, ('one', 47): ('ned', 86): 0, ('ner', 85): 0, ('nes', 26): 0, ('net', 20): 0, ('onic', 94): 0, ('lamer', 117): ('ere', 24): 0, ('ery', 20): 0, ('lat', 106): ('ate', 27): ('tely', 117): 0, ('tene', 74): 0, ('ter', 153): ('era', 35): 0, ('ery', 72): 0, ('ati', 39): ('tice', 187): 0, ('tify', 43): 0, ('til', 25): ('ile', 79): 0, ('ily', 37): 0, ('tin', 230): ('ina', 22): 0, ('ine', 134): 0, ('tise', 129): 0, ('tite', 52): 0, ('tive', 517): 0, ('tize', 59): 0, ('mal', 213): ('ala', 64): ('lace', 67): 0, ('lane', 65): 0, ('lary', 31): 0, ('late', 58): 0, ('ale', 37): ('lene', 90): 0, ('lery', 26): 0, ('mar', 286): ('ara', 21): ('rac', 84): ('ace', 33): 0, ('acy', 60): 0, ('rade', 47): 0, ('rage', 56): 0, ('rate', 76): 0, ('ari', 30): ('rice', 30): 0, ('ride', 25): 0, ('rify', 24): 0, ('rily', 63): 0, ('rin', 156): ('ina', 24): 0, ('ine', 168): 0, ('rise', 146): 0, ('rit', 119): ('ite', 174): 0, ('ity', 118): 0, ('rize', 56): 0, ('nati', 66): ('tice', 187): 0, ('tify', 43): 0, ('til', 25): ('ile', 79): 0, ('ily', 37): 0, ('tin', 230): ('ina', 22): 0, ('ine', 134): 0, ('tise', 129): 0, ('tite', 52): 0, ('tive', 517): 0, ('tize', 59): 0, ('pol', 384): ('ola', 22): ('lane', 27): 0, ('lary', 48): 0, ('late', 165): 0, ('ole', 22): ('lene', 60): 0, ('lery', 39): 0, ('oli', 35): ('lide', 36): 0, ('lify', 24): 0, ('line', 72): 0, ('lise', 79): 0, ('lit', 234): ('ite', 209): 0, ('ity', 698): 0, ('lize', 22): 0, ('oly', 255): , ('refo', 153): , ('rem', 125): ('emer', 26): ('ere', 24): 0, ('ery', 20): 0, ('emi', 32): ('mine', 109): 0, ('mit', 58): ('ite', 64): 0, ('ity', 45): 0, ('emo', 35): ('mony', 133): 0, ('more', 62): 0, ('mose', 29): 0, ('mote', 35): 0, ('rev', 156): ('eve', 76): ('vely', 36): 0, ('very', 139): 0, ('evity', 44): 0, ('evo', 28): , ('uni', 217): ('nimal', 31): 0, ('nine', 62): ('ned', 88): 0, ('ner', 74): 0, ('net', 25): 0, ('niver', 25): 0, ('uranic', 42): 0, ('vale', 78): ('lene', 90): 0, ('lery', 26): 0, ('vene', 111): ('nery', 118): 0, ('nese', 644): 0

【讨论】:

【参考方案3】:

通过结合这三个过程,我能够将速度提高约 25%。

def merge(obj, /):
    result = 
    for key, val in sorted(obj.items()):
        if isinstance(val, dict):
            val = merge(val)
            if not val:
                continue
            if len(val) == 1:
                k1, val = next(iter(val.items()))
                key = (key[0] + k1[0][2:], key[1])
        result[key] = val
    return result

我不知道还有多少可能。

【讨论】:

你的函数已经差不多完美了,但是我需要memoize这个函数,不知道怎么memoize这样的函数不提高unhashable type: 'dict',请你memoize一下好吗? dicts 不可散列,因此您无法记忆它们的函数。最好的选择可能是使用frozendicts。您也可以事先将它们变成它们的项目的元组(例如使用def items(obj, /): return tuple((k, items(v) if isinstance(v, dict) else v) for k, v in dict.items())),然后修改merge 以使用元组。根据其余代码的设计,也许可以将它们创建为元组并避免转换。 我已经在用于构建这些对象的函数中成功实现了您的函数中的过程,该函数使用lru_cache 包装,这是必须的,否则该过程将永远无法完成并使用巨大的RAM,我将新方法的结果与您的函数处理的旧方法的结果进行了比较,它们是相等的。谢谢!

以上是关于Python如何高效处理复杂的嵌套字典的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中处理深度嵌套字典的便捷方法

Python 3:扁平化嵌套字典和字典中的列表

Python:如何将要处理的对象成员(可能嵌套更深)传递给函数?

如何在类中嵌套多处理管理器字典

Python中多个词典和列表字典的高效快速数据存储和处理,以及列表的两个词典的交集

python 第35课 字典_复杂表格数据存储_列表和字典综合嵌套