【问题标题】:Recursively combine dictionaries递归组合字典
【发布时间】:2019-08-09 17:26:08
【问题描述】:

好的,这就是我的想法。我有两个带有对象组的字典,如下所示:

groups = {
    'servers': ['unix_servers', 'windows_servers'],
    'unix_servers': ['server_a', 'server_b', 'server_group'],
    'windows_servers': ['server_c', 'server_d'],
    'server_group': ['server_e', 'server_f']
}

hosts = {
    'server_a': '10.0.0.1',
    'server_b': '10.0.0.2',
    'server_c': '10.0.0.3',
    'server_d': '10.0.0.4',
    'server_e': '10.0.0.5',
    'server_f': '10.0.0.6'
}

我正在寻找的输出是:

d3 = {
    'servers': {
        'unix_servers': {
            'server_a': '10.0.0.1',
            'server_b': '10.0.0.2',
            'server_group': {
                'server_e': '10.0.0.5',
                'server_f': '10.0.0.6'
            }
        },
        'windows_servers': {
            'server_c': '10.0.0.3',
            'server_d': '10.0.0.4'
        }
    }
}

我遇到的问题是我事先不知道有多少递归级别,因为嵌套组理论上可以无限进行。此外,我无法确定哪些键应该是组合字典中的顶级键。

我目前有以下:

def resolve(d1, d2):
for k, v in d1.items():
    for i in v:
        if i in d2.keys():
            d3[k] = {i: d2[i]}

这会返回:

{
  "servers": {
    "unix_servers": {
      "server_a": "10.0.0.1",
      "server_b": "10.0.0.2",
      "server_group": {
        "server_e": "10.0.0.5",
        "server_f": "10.0.0.6"
      }
    },
    "windows_servers": {
      "server_c": "10.0.0.3",
      "server_d": "10.0.0.4"
    }
  },
  "unix_servers": {
    "server_b": "10.0.0.2"
  },
  "windows_servers": {
    "server_d": "10.0.0.4"
  },
  "server_group": {
    "server_f": "10.0.0.6"
  }
}

这很接近,但它显然缺少递归并且不处理键的嵌套。主要是在这里找指针,递归逻辑还没点到我...

【问题讨论】:

    标签: python python-3.x recursion


    【解决方案1】:

    我认为这是你想要的:

    def resolve(groups, hosts):
        # Groups that have already been resolved
        resolved_groups = {}
        # Group names that are not root
        non_root = set()
        # Make dict with resolution of each group
        result = {}
        for name in groups:
            result[name] = _resolve_rec(name, groups, hosts, resolved_groups, non_root)
        # Remove groups that are not root
        for name in groups:
            if name in non_root:
                del result[name]
        return result
    
    def _resolve_rec(name, groups, hosts, resolved_groups, non_root):
        # If group has already been resolved finish
        if name in resolved_groups:
            return resolved_groups[name]
        # If it is a host finish
        if name in hosts:
            return hosts[name]
        # New group resolution
        resolved = {}
        for child in groups[name]:
            # Resolve each child
            resolved[child] = _resolve_rec(child, groups, hosts, resolved_groups, non_root)
            # Mark child as non-root
            non_root.add(child)
        # Save to resolved groups
        resolved_groups[name] = resolved
        return resolved
    

    用你的例子:

    groups = {
        'servers': ['unix_servers', 'windows_servers'],
        'unix_servers': ['server_a', 'server_b', 'server_group'],
        'windows_servers': ['server_c', 'server_d'],
        'server_group': ['server_e', 'server_f']
    }
    
    hosts = {
        'server_a': '10.0.0.1',
        'server_b': '10.0.0.2',
        'server_c': '10.0.0.3',
        'server_d': '10.0.0.4',
        'server_e': '10.0.0.5',
        'server_f': '10.0.0.6'
    }
    
    d3 = {
        'servers': {
            'unix_servers': {
                'server_a': '10.0.0.1',
                'server_b': '10.0.0.2',
                'server_group': {
                    'server_e': '10.0.0.5',
                    'server_f': '10.0.0.6'
                }
            },
            'windows_servers': {
                'server_c': '10.0.0.3',
                'server_d': '10.0.0.4'
            }
        }
    }
    
    
    print(resolve(groups, hosts) == d3)
    # True
    

    请注意,如果您的组 A 包含组 B 但组 B 包含组 A,则对于格式错误的输入,这可能会陷入无限递归。

    【讨论】:

    • 正是我要找的东西,cmets 清楚地解释了这个过程。杰出的。谢谢。
    【解决方案2】:
    d3={}
    main={}
    for i in groups['servers']:
        if(i in groups):
            d={}
            for j in groups[i]:
                if(j in groups):
                    dd={}
                    for k in groups[j]:
                        dd[k]=hosts[k]
                    d[j]=dd
                else:
                    d[j]=hosts[j]
            main[i]=d
    d3['servers']=main
    print(d3)
    

    这会给

    {
        'servers': 
        {
            'unix_servers': 
            {
                'server_group': 
                {
                    'server_f': '10.0.0.6', 
                    'server_e': '10.0.0.5'
                }, 
                'server_b': '10.0.0.2',
                'server_a': '10.0.0.1'
            }, 
            'windows_servers': 
            {
                'server_c': '10.0.0.3', 
                'server_d': '10.0.0.4'
            }
        }
    }
    

    【讨论】:

    • 您可以在这里执行并查看! ide.geeksforgeeks.org/KnY8NBzOq6
    • 这在这种情况下有效,但如果比当前显示的级别更深,则不会递归。
    【解决方案3】:

    我猜它只需要两个循环,不再需要了。在这里,dict 组将被更新,更准确地说,它会丢失一些键值。

    groups = {k: {s: None for s in subs} for k, subs in groups.items()}                                                                                                                                                        
    
    for k, subs in groups.items():                                                                                                                                                                                        
        for subs_k, subs_v in subs.items():                                                                                                                                                                                       
            if subs_v is not None:                                                                                                                                                                                         
                continue                                                                                                                                                                                               
            if subs_k in groups:                                                                                                                                                                                           
                groups[k][subs_k] = groups[subs_k]                                                                                                                                                                             
                del groups[subs_k]                                                                                                                                                                                       
            else:                                                                                                                                                                                                      
                groups[k][subs_k] = hosts[subs_k]
    
    
    print(groups)
    

    你可能想用defaultdict重写它

    【讨论】:

      【解决方案4】:

      假设您对可能具有交叉引用数据结构感到满意,那么您不一定需要在此处使用递归。

      from itertools import chain
      
      group_dicts = {k: {} for k in groups}
      
      for g in group_dicts:
          for child_key in groups[g]:
              child = group_dicts.get(child_key, hosts.get(child_key))
              group_dicts[g][child_key] = child
      
      # remove entries that are referenced at least once
      not_top_levels = set(chain.from_iterable(groups.values()))
      result = {g: group_dicts[g] for g in group_dicts if g not in not_top_levels}
      

      与其他解决方案不同,这将正确处理循环和无限递归组,因为所有 dict 引用都是共享的。当您的groups 在拓扑上描述一棵树时,这将与递归解决方案完全相同。但是,如果您的groups 在拓扑上描述了一个有向无环图,则此解决方案将共享所有出现多次的节点的字典,而递归解决方案会将重复项复制并扩展为常规树,这不会如果您不需要改变字典,这确实是一个问题。如果您的groups 在拓扑上描述了一个带有循环的图,那么这将创建这些循环,而递归解决方案将由于无限递归而下降。

      【讨论】:

        【解决方案5】:

        你可以使用简单的递归:

        def build(val): 
          return {i:build(i) for i in groups[val]} if val in groups else hosts[val]
        

        import json
        print(json.dumps({'servers':build('servers')}, indent=4))
        

        输出:

        {
          "servers": {
            "unix_servers": {
                "server_a": "10.0.0.1",
                "server_b": "10.0.0.2",
                "server_group": {
                    "server_e": "10.0.0.5",
                    "server_f": "10.0.0.6"
                }
            },
            "windows_servers": {
                "server_c": "10.0.0.3",
                "server_d": "10.0.0.4"
             }
          }
        }
        

        【讨论】:

        • 这是递归真正大放异彩的场景之一。整个程序的复杂性几乎为零。漂亮的答案。
        【解决方案6】:

        python新手我一直在苦苦挣扎,但终于找到了解决方案,虽然它很长。

        groups = {
            'servers': ['unix_servers', 'windows_servers'],
            'unix_servers': ['server_a', 'server_b', 'server_group'],
            'windows_servers': ['server_c', 'server_d'],
            'server_group': ['server_e', 'server_f']
        }
        
        hosts = {
            'server_a': '10.0.0.1',
            'server_b': '10.0.0.2',
            'server_c': '10.0.0.3',
            'server_d': '10.0.0.4',
            'server_e': '10.0.0.5',
            'server_f': '10.0.0.6'
        }
        
        result = {}
        
        parent = '';
        
        levels = [];
        
        def check(s,r):
            if(r[s]==''): #check for each blank element in the result recursively
                sublist = {}
                for k in groups[s]: # check if the key exist in group if exist append children to result.   
                    if k in hosts :
                        sublist[k] = hosts[k] # check if host exist in hosts for this key
                    else:
                        sublist[k]=''
                if(key_exist(result, s)): # check if the key exist in result if exist append to result.             
                    d = result 
                    p = None
                    for key in levels:
                        p = d
                        d = d[key]
                    p[key] = sublist
                    del levels[:]
                for x in sublist :
                    if(sublist[x] == ''):
                        check(x,p[key])
        
        def resolve(r):
            for s in r:
                if(isinstance(r[s],dict)):
                    return resolve(r[s])
                else:
                    check(s,r)
        
        
        def key_exist(groups, key): 
            for s in groups:
                if key in groups:
                    levels.append(key);
                    return True
                else:
                    if(isinstance(groups[s],dict)):
                        levels.append(s);
                        return key_exist(groups[s], key)
            return False
        
        # Find the root or parent element
        for k in groups.keys(): 
            found = False
            for j in groups.keys(): 
                if k in groups[j]:
                    found = False
                else:
                    found = True
            if(found):
                parent = k  # root or parent element
        
        # start making result with root element 
        s = {}
        for k in groups[parent]:
            s[k]='' # initialize child elements with blank
        result[parent] = s 
        
        resolve(result) 
        
        print(result)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-10
          • 2012-11-11
          • 2017-07-29
          • 2011-08-31
          相关资源
          最近更新 更多