【问题标题】:Python: How to convert nested lists inside nested dicts into dictsPython:如何将嵌套字典中的嵌套列表转换为字典
【发布时间】:2023-03-11 09:51:01
【问题描述】:

所以我有这个字典:

di = {'Type': ['Something1'],
      'details': [{'detail': [{'category': ['Stuff1'], 'value': ['Value1']},
                              {'category': ['Stuff2'], 'value': ['Value2']},
                              {'category': ['Stuff3'], 'value': ['Value3']},
                              {'category': ['Stuff3'], 'value': ['Value3']},
                              {'category': ['Stuff4'], 'value': ['Value4']}]}],
      'timestamp': ['2018-01-22 07:10:41']}

并希望将任何list(除了其中不包含另一个list of dicts 的任何list of dicts 除外)转换为dict,这样最终结果将是:

{'Type': 'Something1',
 'details': {'detail': [{'category': 'Stuff1', 'value': 'Value1'},
                        {'category': 'Stuff2', 'value': 'Value2'},
                        {'category': 'Stuff3', 'value': 'Value3'},
                        {'category': 'Stuff3', 'value': 'Value3'},
                        {'category': 'Stuff4', 'value': 'Value4'}]},
 'timestamp': '2018-01-22 07:10:41'}

因此,本质上,对于值为单个项目list 的任何键,该值应删除list 组件。

我尝试了以下递归函数但没有成功:

def delistdict(dicto):

    delisted = {}

    for k,v in dicto.items():

        if isinstance(v, list) and len(v) == 1:  
            delisted[k] = v[0]

        else:
            delisted[k] = delistdict(v)

    return {k:v if len(v) == 1 else v for k,v in delisted.items()}

它失败了,因为它只删除了 {'detail': [(...)]list 的第一个实例(所以只是那个外部 [(...)] 列表),但它不会递归到剩余物品。所以我运行脚本后的结果是这样的:

{'Type': 'Something1',
 'details': {'detail': [{'category': ['Stuff1'], 'value': ['Value1']},
                        {'category': ['Stuff2'], 'value': ['Value2']},
                        {'category': ['Stuff3'], 'value': ['Value3']},
                        {'category': ['Stuff3'], 'value': ['Value3']},
                        {'category': ['Stuff4'], 'value': ['Value4']}]},
 'timestamp': '2018-01-22 07:10:41'}

应该发生的情况是 valuecategory 键中的单个值应转换为 strings,而不是在 list 中保留为单个项目。

任何想法我做错了什么?

【问题讨论】:

  • 如果一个值是一个字典,那么递归地应用你的函数。

标签: python list dictionary recursion


【解决方案1】:

你可以试试这个:

di = {'timestamp': ['2018-01-22 07:10:41'], 'Type': ['Something1'], 'details': [{'detail': [{'category': ['Stuff1'], 'value': ['Value1']}, {'category': ['Stuff2'], 'value': ['Value2']}, {'category': ['Stuff3'], 'value': ['Value3']}, {'category': ['Stuff3'], 'value': ['Value3']}, {'category': ['Stuff4'], 'value': ['Value4']}]}]}
def flatten(d):
  return {a:b[0] if len(b) == 1 and isinstance(b[0], str) else (flatten(b[0]) if len(b) == 1 and isinstance(b[0], dict) else [flatten(c) for c in b]) for a, b in d.items()}

输出:

{'timestamp': '2018-01-22 07:10:41', 'Type': 'Something1', 'details': {'detail': [{'category': 'Stuff1', 'value': 'Value1'}, {'category': 'Stuff2', 'value': 'Value2'}, {'category': 'Stuff3', 'value': 'Value3'}, {'category': 'Stuff3', 'value': 'Value3'}, {'category': 'Stuff4', 'value': 'Value4'}]}}

不解解:

def flatten(d):
   new_d = {}
   for a, b in d.items():
      if len(b) == 1 and isinstance(b[0], str):
          new_d[a] = b[0]
      elif len(b) == 1 and isinstance(b[0], dict):
          new_d[a] = flatten(b[0])
      else:
          temp_list = []
          for c in b:
             temp_list.append(flatten(c))
          new_d[a] = temp_list
   return new_d

输出:

{'timestamp': '2018-01-22 07:10:41', 'Type': 'Something1', 'details': {'detail': [{'category': 'Stuff1', 'value': 'Value1'}, {'category': 'Stuff2', 'value': 'Value2'}, {'category': 'Stuff3', 'value': 'Value3'}, {'category': 'Stuff3', 'value': 'Value3'}, {'category': 'Stuff4', 'value': 'Value4'}]}}

【讨论】:

  • 因此您的解决方案适用于末端“叶”节点,但仍然未满足条件,即应删除内部仅包含一项的任何列表并且仅保留内部项,即使此“一个”项是一个字典。为了使您的结果成为我正在寻找的脚本,还应该删除以第一个“详细信息”开头的 ['details': [{'detail': [{'category': 'Stuff1', 'value': 'Value1'}。我认为您的方向是正确的!,您能否分享它的非字典理解版本,以便我能更好地理解?
  • 这很好,很简洁,但如果数据不是str 类型,它可能会引起轰动。 dict 理解版本太疯狂了!
  • @Ajax1234 你做到了,伙计!我没有选择它作为“答案”的唯一原因是,即使它比我的代码好得多,它也太复杂了,我有限的头脑无法理解它。如果您将其发布为没有“理解”的完全成熟的功能,我会接受它。感谢您的及时回复!
  • @quassar 很高兴为您提供帮助!我从“非理解”解决方案中删除了所有理解。
【解决方案2】:

我没有花时间优化代码,但我修改了你原来的函数来做到这一点:

def delistdict(dicto):

    delisted = {}

    for k, v in dicto.items():

        if isinstance(v, list):
            lst = []
            for i in v:
                if isinstance(i, dict):
                    lst.append(delistdict(i))
                else:
                    lst.append(i)
            delisted[k] = lst[0] if len(lst) == 1 else lst
        elif isinstance(v, dict):
            delisted[k] = delistdict(v)
        else:
            delisted[k] = v

    return {k:v if len(v) == 1 else v for k,v in delisted.items()}

输出(漂亮的打印):

{'Type': 'Something1',
 'details': {'detail': [{'category': 'Stuff1', 'value': 'Value1'},
                        {'category': 'Stuff2', 'value': 'Value2'},
                        {'category': 'Stuff3', 'value': 'Value3'},
                        {'category': 'Stuff3', 'value': 'Value3'},
                        {'category': 'Stuff4', 'value': 'Value4'}]},
 'timestamp': '2018-01-22 07:10:41'}

问题是您的函数假定所有 list 都应该有 len 1,否则它只会返回已删除的字典,其中仍然有嵌套的 lists 内部未处理。

【讨论】:

  • 感谢您显示我的错误在哪里,这也是我的问题的一部分,因为我很难理解递归函数是如何工作的。当我试图理解你的答案时,我会接受它。谢谢! (无法投票,因为我没有足够的声誉抱歉)
  • 谢谢!我必须承认@Ajax1234 的代码更加简洁明了(理解和非理解版本)。不过,我要给您的一个提示是查看 pprint 模块,它有助于使您的列表/字典更具可读性并有助于调试,尤其是对于冗长和嵌套的对象。
  • 如果您同意,那么我将选择@Ajax1234 答案,但您的答案一直是最能帮助我理解递归函数的答案;)
猜你喜欢
  • 2022-12-05
  • 2020-09-03
  • 1970-01-01
  • 1970-01-01
  • 2020-05-16
  • 2021-09-25
  • 1970-01-01
  • 2021-08-09
  • 1970-01-01
相关资源
最近更新 更多