【问题标题】:Convert a nested dict to a dict that only include list items that has a unique set of keys将嵌套 dict 转换为仅包含具有唯一键集的列表项的 dict
【发布时间】:2016-05-06 19:25:20
【问题描述】:

如何将以下嵌套 dict 转换为仅包含具有唯一键集(无论值如何)的列表项的 dict?

我不知道有多少层嵌套,也不关心返回的是哪个列表项,只要它有一组唯一的键(对于该项所属的列表)

(我正在尝试从一个非常长的 YAML 文件中生成一个示例文件以用于文档记录)

input = { 
   "mylist": [
      {
         "key1": "1333", 
         "key2": [
                  { 
                   "key2a":134,
                   "key2b":1373
                    },
                  { 
                   "key2a":124,
                   "key2b":136
                    }
                  ]
      },{
         "key1": "875", 
         "key2": [
                  { 
                   "key2a":999,
                   "key2b":6567
                    },
                  { 
                   "key2a":8765,
                   "key2b":875
                    }
                  ]
      },{
         "key1": "6754", 
         "key3": 3232
      },{
         "key1": "34545", 
         "key3": 34554
      }
   ]
 }

需要的输出:

{ 
   "mylist": [
      {
         "key1": "1333", 
         "key2": [
                  { "key2a":134,
                   "key2b":1373
                    }
                  ]
      },{
         "key1": "6754", 
         "key3": 3232
      }
   ]
}

我编写了这个(冗长的)代码,通过获取和存储它在列表项对象中找到的所有键来解决它,但我确信这可以用更短的方式完成?

input = collections.OrderedDict(input)
def get_keys(obj,keys=[]):
    if isinstance(obj, (dict,collections.OrderedDict)):
        for k, v in obj.items():
            if not isinstance(v, (dict,collections.OrderedDict)):
                keys.append(k)        
            get_keys(v,keys)
    elif isinstance(obj, list):
        for elem in obj:
            if not isinstance(elem, (dict,collections.OrderedDict,list)):
                keys.append(elem)
            get_keys(elem,keys)
    return keys

def traverse(obj,  callback=None):
    if isinstance(obj, (dict,collections.OrderedDict)):        
        value = {k: traverse(v, callback)
                 for k, v in obj.items()}
    elif isinstance(obj, list):
        value = [traverse(elem,  callback)
                 for elem in obj]
    else:
        value = obj
    if callback is None:
        return value
    else:
        return callback(value)

def traverse_modify(obj):
    def yaml_shortener(obj):
        duplicates = []
        if isinstance(obj,list) and len(obj)>1:
            return_list = []
            for i,elem in enumerate(obj):
                if not any(Counter(get_keys(elem,keys=[])) == Counter(item) for item in duplicates): 
                    return_list.append(elem)
                    duplicates.append(get_keys(elem,keys=[]))       
            return return_list
        else:
            return obj
    return traverse(obj, callback=yaml_shortener)   

def shorten_yaml(obj):
    return traverse_modify(obj)

print json.dumps(shorten_yaml(input),indent=3)

【问题讨论】:

  • 选择条件是什么?例如,为什么"key2a":999 被拒绝而"key2a":134 被记录?
  • 手工编辑的 json :) 值无关紧要。如果一个列表有许多具有相同键结构的项目,则删除除一个之外的所有项目。希望它有意义。
  • 所以本质上,它只是随机选择 1 个密钥。换句话说,如果有 4 个 'key2a' 实例,只需保存其中一个,其余的忽略。对吗?
  • 我认为关键是必须保持列表中项目的结构完整性,我不能从列表中的单个 dict-items 中删除键。但是可以肯定的是,如果列表中有两个(或更多)项目由 'key2a', [{'key2a':1},{'key2a:2}] 组成,那么除了一个之外的所有项目都可以删除
  • 为什么是"key1": "875"?不见了?

标签: python json yaml


【解决方案1】:

首先我假设与字典键关联的值是 字典列表或 标量 值。该值由函数处理 convert_value(),其中:

  • 如果值为字典列表,则调用函数 process_dict_list() 以提取唯一的键集;
  • 如果是标量,则保持值不变。

魔法发生在函数 convert_dict_list() 中,它:

  • 收集传递的字典列表的每个字典的所有键(和值);
  • 对它们进行排序并创建一个临时字典,将每个键元组映射到相应的转换值列表中;这样的字典当然包含唯一的键元组,完全符合要求;
  • 最后,键元组和值列表被转换回预期的字典。

函数 convert() 是一个简单的接口函数,接受带有输入数据的字典:它只是将字典转换为字典列表(带有单个项目)并调用前面的函数来处理它。

以下是完整代码:

def convert(a_dict):
    return convert_dict_list([a_dict])[0]

def convert_dict_list(dict_list):
    sorted_items = (zip(*sorted(d.iteritems())) for d in dict_list)
    tmp_dict = dict((tuple(keys), map(convert_value, values))
        for (keys, values) in sorted_items)
    return map(dict, [zip(k, v) for (k, v) in tmp_dict.iteritems()])

def convert_value(val):
    return convert_dict_list(val) if isinstance(val, list) else val

这是使用示例输入生成的输出:

>>> print convert(input)
{'mylist': [{'key3': 34554, 'key1': '34545'}, {'key2': [{'key2b': 875, 'key2a': 8765}], 'key1': '875'}]} 

【讨论】:

  • 如果 keys 有 dict 作为值,它甚至可以工作,就像我添加 ,{"key4": { "key5":23}},{"key4": { "key5":44} } 它将正确删除“key4”之一。
猜你喜欢
  • 2018-12-03
  • 1970-01-01
  • 2018-09-07
  • 2022-07-08
  • 2021-06-02
  • 2021-08-24
  • 2018-10-14
  • 2018-08-10
  • 1970-01-01
相关资源
最近更新 更多