Denormalize/flatten 嵌套对象列表,以点分隔的键值对
Denormalize/flatten list of nested objects into dot separated key value pairs
如果我的嵌套对象是字典会更简单,但这些是字典列表。
示例:
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 库,因为我的应用程序部署在 App Engine 上。
使用此代码获得您想要的输出。它根据递归调用生成输出。
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 )
让我知道它是否适合你。
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 评论中提到的情况(对于一个输入字典有多个键,其值由一个列表组成不止一个元素),可以通过对 all_objs 中的 "d" 键进行反注释来测试。另外,理论上它应该支持任何深度
- flatten_dict:接受一个输入字典并输出一个字典列表(因为输入字典可能产生多个输出字典):
- 每个具有 "simple"(不是列表)值的键都进入输出字典(y/ies)不变
- 至此,一个base的输出字典就完成了(如果输入字典会生成多于输出字典,所有都会有base字典keys/values,如果只生成一个输出字典,那么就是base那个)
- 接下来,处理具有 "problematic" 个值的键 - 可能会生成多个输出字典 - (如果有的话):
- 具有单个元素列表的键 ("problematic") - 每个 可能 生成多个输出字典:
- 每个值都将被展平(可能产生多个输出字典);过程中会用到对应的key
- 然后,将在所有扁平化字典列表上计算笛卡尔积(对于当前输入,将只有一个列表包含一个元素)
- 现在,每个产品项目都需要在 distinct 输出字典中,因此 base 字典被复制并使用键更新/ 产品项目中 每个 元素的值(对于当前输入,每个产品项目只有一个元素)
- 新词典已附加到列表
- 至此,base 字典的列表(可能只有一个)就完成了,如果不存在由具有多个元素的列表组成的值,这就是 return 列表,否则必须为列表
中的 each base 字典完成以下所有操作
- 具有包含更多元素的列表的键 ("very problematic") - 每个 将 生成多个输出字典:
- 首先,将针对所有值(包含多个元素的列表)计算笛卡尔积。在当前情况下,因为它只有一个这样的列表,所以每个产品项目将只包含该列表中的一个元素
- 然后,对于每一个product item元素,需要根据lists顺序建立key(对于当前输入,product item只包含一个元素,也只有一个key)
- 同样,每个产品项目都需要在 distinct 输出字典中,因此 base 字典被复制并使用键更新/ 扁平化产品的值
- 新词典附加到输出词典列表
- 适用于 Python 3 和 Python 2
- 可能会很慢(尤其是对于大输入对象),因为性能不是目标。此外,由于它是自下而上构建的(在处理新案例时添加功能),它非常扭曲(RO:intortocheated :)),我可能错过了一个更简单的实现。
输出:
c:\Work\Dev\Whosebug\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:
- 使其更通用(尽管它对当前输入不可见):仅包含一个元素的值可以产生比输出字典更多的值(展平时),解决了这种情况(在我只考虑 1[=53 之前) =]st输出字典,其他的直接忽略)
- 更正了一个逻辑错误,该错误被屏蔽掉了与笛卡尔积相结合的元组解包:
if not complex_items ...
部分
@EDIT1:
- 修改了代码以匹配要求更改:扁平化字典中的键必须在输入字典中具有完整嵌套路径
如果我的嵌套对象是字典会更简单,但这些是字典列表。 示例:
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 库,因为我的应用程序部署在 App Engine 上。
使用此代码获得您想要的输出。它根据递归调用生成输出。
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 )
让我知道它是否适合你。
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 评论中提到的情况(对于一个输入字典有多个键,其值由一个列表组成不止一个元素),可以通过对 all_objs 中的 "d" 键进行反注释来测试。另外,理论上它应该支持任何深度
- flatten_dict:接受一个输入字典并输出一个字典列表(因为输入字典可能产生多个输出字典):
- 每个具有 "simple"(不是列表)值的键都进入输出字典(y/ies)不变
- 至此,一个base的输出字典就完成了(如果输入字典会生成多于输出字典,所有都会有base字典keys/values,如果只生成一个输出字典,那么就是base那个)
- 接下来,处理具有 "problematic" 个值的键 - 可能会生成多个输出字典 - (如果有的话):
- 具有单个元素列表的键 ("problematic") - 每个 可能 生成多个输出字典:
- 每个值都将被展平(可能产生多个输出字典);过程中会用到对应的key
- 然后,将在所有扁平化字典列表上计算笛卡尔积(对于当前输入,将只有一个列表包含一个元素)
- 现在,每个产品项目都需要在 distinct 输出字典中,因此 base 字典被复制并使用键更新/ 产品项目中 每个 元素的值(对于当前输入,每个产品项目只有一个元素)
- 新词典已附加到列表
- 至此,base 字典的列表(可能只有一个)就完成了,如果不存在由具有多个元素的列表组成的值,这就是 return 列表,否则必须为列表 中的 each base 字典完成以下所有操作
- 具有包含更多元素的列表的键 ("very problematic") - 每个 将 生成多个输出字典:
- 首先,将针对所有值(包含多个元素的列表)计算笛卡尔积。在当前情况下,因为它只有一个这样的列表,所以每个产品项目将只包含该列表中的一个元素
- 然后,对于每一个product item元素,需要根据lists顺序建立key(对于当前输入,product item只包含一个元素,也只有一个key)
- 同样,每个产品项目都需要在 distinct 输出字典中,因此 base 字典被复制并使用键更新/ 扁平化产品的值
- 新词典附加到输出词典列表
- 具有单个元素列表的键 ("problematic") - 每个 可能 生成多个输出字典:
- 适用于 Python 3 和 Python 2
- 可能会很慢(尤其是对于大输入对象),因为性能不是目标。此外,由于它是自下而上构建的(在处理新案例时添加功能),它非常扭曲(RO:intortocheated :)),我可能错过了一个更简单的实现。
输出:
c:\Work\Dev\Whosebug\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:
- 使其更通用(尽管它对当前输入不可见):仅包含一个元素的值可以产生比输出字典更多的值(展平时),解决了这种情况(在我只考虑 1[=53 之前) =]st输出字典,其他的直接忽略)
- 更正了一个逻辑错误,该错误被屏蔽掉了与笛卡尔积相结合的元组解包:
if not complex_items ...
部分
@EDIT1:
- 修改了代码以匹配要求更改:扁平化字典中的键必须在输入字典中具有完整嵌套路径