【问题标题】:Denormalize/flatten list of nested objects into dot separated key value pairs将嵌套对象列表非规范化/展平为点分隔键值对
【发布时间】:2018-03-02 16:24:33
【问题描述】:

如果我的嵌套对象是字典会更简单,但这些是字典列表。 示例:

all_objs1 = [{
    'a': 1,
    'b': [{'ba': 2, 'bb': 3}, {'ba': 21, 'bb': 31}],
    'c': 4
}, {
    'a': 11,
    'b': [{'ba': 22, 'bb': 33, 'bc': [{'h': 1, 'e': 2}]}],
    'c': 44
}]

我希望输出格式如下:

[
  {'a': 1, 'b.ba': 2, 'b.bb': 3, 'c': 4},
  {'a': 1, 'b.ba': 21, 'b.bb': 31, 'c': 4},
  {'a': 11, 'b.ba': 22, 'b.bb': 33, 'bc.h': 1, 'bc.e': 2, 'c': 44},
]

基本上,生成的展平对象的数量将等于 (obj * depth)

使用我当前的代码:

def flatten(obj, flattened_obj, last_key=''):
  for k,v in obj.iteritems():
    if not isinstance(v, list):
      flattened_obj.update({last_key+k : v})
    else:
      last_key += k + '.'
      for nest_obj in v:
        flatten(nest_obj, flattened_obj, last_key)
        last_key = remove_last_key(last_key)

def remove_last_key(key_path):
    second_dot = key_path[:-1].rfind('.')
    if second_dot > 0:
      return key_path[:second_dot+1]
    return key_path

输出:


[
  {'a': 1, 'b.bb': 31, 'c': 4, 'b.ba': 21},
  {'a': 11, 'b.bc.e': 2, 'c': 44, 'b.bc.h': 1, 'b.bb': 33, 'b.ba': 22}
]

我能够展平对象(虽然不准确),但我无法在每个嵌套对象上创建一个新对象。 由于我的应用部署在应用引擎上,因此我无法使用 pandas 库。

【问题讨论】:

  • 我看到一个复杂的对象(一个dict)总是被封装在一个列表中。如果在输入中你会发生什么,例如"d" : {"da": 4, "db": 6}?这可能吗?
  • 不,嵌套对象将始终是列表中的对象。值始终是对象列表或字符串/数字。
  • 另一个问题:例如,如果输入中的第一个对象会有多个列表字段怎么办?例如"d": [{"da": 1, "db": 2},{"da": 3, "db": 4}],这会在输出中产生 4 个条目吗?
  • @CristiFati,首先只能有一个包含更多字典列表的键。但是这些字典可以以同样的方式进一步嵌套。所以在第一个对象中,如果集合在“b”处,它不能有任何其他的集合键。

标签: python json recursion flatten denormalized


【解决方案1】:

使用此代码获得所需的输出。它基于递归调用生成输出。

import json
from copy import deepcopy
def flatten(final_list, all_obj, temp_dct, last_key):


    for dct in all_obj:
        deep_temp_dct = deepcopy(temp_dct)
        for k, v in dct.items():
            if isinstance(v, list):
                final_list, deep_temp_dct = flatten(final_list, v, deep_temp_dct, k)
            else:
                prefix = ""
                if last_key : prefix = last_key + "."
                key = prefix+ k
                deep_temp_dct[key] = v
        if deep_temp_dct not in final_list:
            final_list.append(deep_temp_dct)

    return final_list, deep_temp_dct

final_list, _ = flatten([], all_objs1, {}, "")
print json.dumps(final_list, indent =4 )

让我知道它是否适合你。

【讨论】:

  • 这个样本不能很好地工作:[{'a': 1, 'c': [{'aa': 11, 'bb': 22}, {'aa': 101, 'bb': 202}], 'b': 2}]
    输出结果: [ { "a": 1, "c.aa": 11, "c.bb": 22 }, { "a": 1, "c.aa": 101, "c.bb": 202, "b": 2 } ] ,即在第一个生成的平面对象中缺少 "b": 2。
  • @Madhukar,它有时不会遍历键,值在集合值之后。
【解决方案2】:

code.py

from itertools import product
from pprint import pprint as pp


all_objs = [{
    "a": 1,
    "b": [{"ba": 2, "bb": 3}, {"ba": 21, "bb": 31}],
    "c": 4,
    #"d": [{"da": 2}, {"da": 5}],
}, {
    "a": 11,
    "b": [{"ba": 22, "bb": 33, "bc": [{"h": 1, "e": 2}]}],
    "c": 44,
}]


def flatten_dict(obj, parent_key=None):
    base_dict = dict()
    complex_items = list()
    very_complex_items = list()
    for key, val in obj.items():
        new_key = ".".join((parent_key, key)) if parent_key is not None else key
        if isinstance(val, list):
            if len(val) > 1:
                very_complex_items.append((key, val))
            else:
                complex_items.append((key, val))
        else:
            base_dict[new_key] = val
    if not complex_items and not very_complex_items:
        return [base_dict]
    base_dicts = list()
    partial_dicts = list()
    for key, val in complex_items:
        partial_dicts.append(flatten_dict(val[0], parent_key=new_key))
    for product_tuple in product(*tuple(partial_dicts)):
        new_base_dict = base_dict.copy()
        for new_dict in product_tuple:
            new_base_dict.update(new_dict)
        base_dicts.append(new_base_dict)
    if not very_complex_items:
        return base_dicts
    ret = list()
    very_complex_keys = [item[0] for item in very_complex_items]
    very_complex_vals = tuple([item[1] for item in very_complex_items])
    for product_tuple in product(*very_complex_vals):
        for base_dict in base_dicts:
            new_dict = base_dict.copy()
            new_items = zip(very_complex_keys, product_tuple)
            for key, val in new_items:
                new_key = ".".join((parent_key, key)) if parent_key is not None else key
                new_dict.update(flatten_dict(val, parent_key=new_key)[0])
            ret.append(new_dict)
    return ret


def main():
    flatten = list()
    for obj in all_objs:
        flatten.extend(flatten_dict(obj))
    pp(flatten)


if __name__ == "__main__":
    main()

注意事项

  • 正如预期的那样,使用了递归
  • 一般来说,它也适用于我在我的 2nd 评论中提到的情况(对于一个输入 dict 具有多个键,其值由具有多个元素的列表组成) ,可以通过在 all_objs 中取消 "d" 键来测试。此外,理论上它应该支持任何深度
  • flatten_dict:接受输入字典并输出字典列表(因为输入字典可能会产生多个输出字典):
    • 具有“简单”(非列表)值的每个键都将进入输出字典 (y/ies) 而不更改
    • 此时,一个 base 输出字典已经完成(如果输入字典会生成多个输出字典,则所有字典都将具有 base 字典键/值,如果它只生成一个输出字典,那将是 base 一个)
    • 接下来,处理具有“问题”值的键(可能生成的不仅仅是输出字典)(如果有的话):
      • 具有单个元素的列表的键(“有问题”) - 每个 可能 生成多个输出字典:
        • 每个值都将被展平(可能产生多个输出字典);过程中会用到对应的key
        • 然后,将在所有展平的字典列表上计算笛卡尔积(对于当前输入,只有一个包含一个元素的列表)
        • 现在,每个产品项都需要在 distinct 输出字典中,因此 base 字典被复制并使用 every 产品项中的元素(对于当前输入,每个产品项将只有一个元素)
        • 新字典已附加到列表中
      • 此时 base 字典列表(可能只有一个)已完成,如果不存在由包含多个元素的列表组成的值,则这是返回列表,否则以下所有内容必须为列表中的每个 base字典完成
      • 具有包含更多元素的列表的键(“非常有问题”) - 每个生成多个输出字典:
        • 首先,将根据所有值(具有多个元素的列表)计算笛卡尔积。在当前情况下,由于它只是一个这样的列表,因此每个产品项将只包含该列表中的一个元素
        • 那么,对于每个产品项元素,需要根据列表顺序建立其key(对于当前输入,产品项将只包含一个元素,并且也将只有一个key)李>
        • 同样,每个产品项都需要在 distinct 输出字典中,因此 base 字典被复制并使用展平产品项的键/值进行更新
      • 新字典被追加到输出字典列表中
  • 适用于 Python 3Python 2
  • 可能会很慢(尤其是对于大型输入对象),因为性能不是目标。此外,由于它是自下而上构建的(在处理新案例时添加功能),它非常扭曲(RO:intortocheated :)),我可能错过了一个更简单的实现。

输出

c:\Work\Dev\StackOverflow\q046341856>c:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe code.py
[{'a': 1, 'b.ba': 2, 'b.bb': 3, 'c': 4},
 {'a': 1, 'b.ba': 21, 'b.bb': 31, 'c': 4},
 {'a': 11, 'b.ba': 22, 'b.bb': 33, 'b.bc.e': 2, 'b.bc.h': 1, 'c': 44}]

@EDIT0

  • 使其更通用(尽管它对当前输入不可见):仅包含一个元素的值可以产生比输出字典更多的值(展平时),解决了这种情况(在我只考虑 1st 输出字典,忽略其余部分)
  • 更正了与笛卡尔积相结合的元组解包被屏蔽的逻辑错误:if not complex_items ... 部分

@EDIT1

  • 修改了代码以匹配需求更改:展平字典中的键必须具有输入字典中的完整嵌套路径

【讨论】:

  • {'a': 11, 'b.ba': 22, 'b.bb': 33, 'bc.e': 2, 'bc.h': 1, 'c' : 44}] 这个输出对象不正确,应该是 {'a': 11, 'b.ba': 22, 'b.bb': 33, 'b.bc.e': 2, 'b. bc.h': 1, 'c': 44}] 所有级别的嵌套都应该保持其完整的路径。
  • 解决方案效果很好,只需稍作改动。非常感谢:)
  • 虽然它在实际输出中,但完整的密钥路径不是在预期输出中的要求 ('bc.h')。我发现这有点奇怪,但我认为这是要求。无论如何,当深入递归时,parent_key 必须设置为new_key 而不是key。将相应地编辑答案。
猜你喜欢
  • 2020-05-06
  • 2021-08-20
  • 1970-01-01
  • 2012-01-28
  • 2019-10-11
  • 2016-06-24
  • 2017-05-02
  • 1970-01-01
相关资源
最近更新 更多