我怎样才能 "squash" 字典列表?
How can I "squash" a list of dictionaries?
抱歉标题不明确,但我不确定如何描述我正在尝试执行的操作。
django-auditlog 在格式为 {'field_name': [old_value, new_value]}
的 Django 模型中生成 "diffs" 个跟踪字段,这些字段在数据库中更改时跟踪字段。因此,我数据库中特定行上的这些差异列表,首先按照最近的差异排序,可能如下所示:
# 1
[
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
我想 "squash" 像在 Git 中那样处理此历史记录:获取字段的第一个值和字段的最新值,并删除所有中间值。所以在上面的例子中,我想要以下输出:
# 2
{
'price': [0, 530],
'status': [10, 1],
'location': [None, 'Calgary']
}
请注意,多个 'status'
和 'price'
更改已压缩为单个 old/new 对。
我相信我可以通过首先创建一个中间字典来完成此操作,其中 所有 更改被串联起来:
# 3
{
'price': [[0, 490], [490, 530]],
'status': [[10, 1], [1, 7], [7, 1]],
'location': [[None, 'Calgary']]
}
然后提取每个字典元素的第一个列表元素的第一个列表元素,以及每个字典元素的最后一个列表元素的最后一个列表元素。
什么是让 #1
看起来像 #3
的干净且 Pythonic 的方法?
在显示的示例数据中,更改按时间倒序列出。只需遍历构建一组合并字段的列表:每个重复的字段都会更新 'old' 值,'new' 来自第一个更改。
changes = [
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
squashed = {}
for delta in changes:
for field, values in delta.items():
if field in squashed:
squashed[field][0] = values[0]
else:
squashed[field] = values
产生以下结果:
In [7]: print(squashed)
{'status': [10, 1], 'location': [None, 'Calgary'], 'price': [0, 530]}
您可以直接转到#2。在遍历 #1 时,如果密钥是新的,则创建一个新条目,并且只更新结束状态:
l = [
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
l.reverse()
squashed = {}
for x in l:
for k,v in x.items():
squashed.setdefault(k, [v[0],v[1]])
squashed[k][1] = v[1]
print squashed
dict.setdefault()
可能对您有用:
from pprint import pprint
one = [
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
two = {}
for d in one:
for k,v in d.items():
two.setdefault(k, v)[0] = v[0]
pprint(two)
结果:
{'location': [None, 'Calgary'], 'price': [0, 530], 'status': [10, 1]}
考虑更新列表:
crunch=lambda d,u: dict(d.items()+[(k, [u[k][0], d.get(k, u[k])[1]]) for k in u])
reduce(crunch, l)
这给你:
{'location': [None, 'Calgary'], 'price': [0, 530], 'status': [10, 1]}
因此,reduce 函数的第一个参数是一个函数,它接收按以下方式从列表中获取的一对参数:
l = [ 0, 1, 2, 3 ]
reduce( f, l ) == f( f ( f( f(0, 1), 2), 3)
通过这种方式,lambda 函数接收一个增量构建的字典作为第一个参数 (d),并通过迭代 u 中的更新来构建一个新的更新字典。
lambda 函数变得过于复杂,因为更新方法不是 return 字典而是 None,所以它正在构建一个新字典,而只是为了能够 return它。
您可以将 lambda 替换为实际函数,作为更清晰的替代方案,可以轻松 return 更新字典:
def crunch(dic, updates):
dic.update(
{ k: [updates[k][0], dic.get(k, updates[k])[1]] for k in updates }
)
return dic # gonna be the input of the next iteration
然后做:
reduce(crunch, l)
字典的 get 方法 return如果 k 存在则为项目值,如果不存在则将第二个参数作为默认值,因此它不需要 defaultdict 或 setdefault。
抱歉标题不明确,但我不确定如何描述我正在尝试执行的操作。
django-auditlog 在格式为 {'field_name': [old_value, new_value]}
的 Django 模型中生成 "diffs" 个跟踪字段,这些字段在数据库中更改时跟踪字段。因此,我数据库中特定行上的这些差异列表,首先按照最近的差异排序,可能如下所示:
# 1
[
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
我想 "squash" 像在 Git 中那样处理此历史记录:获取字段的第一个值和字段的最新值,并删除所有中间值。所以在上面的例子中,我想要以下输出:
# 2
{
'price': [0, 530],
'status': [10, 1],
'location': [None, 'Calgary']
}
请注意,多个 'status'
和 'price'
更改已压缩为单个 old/new 对。
我相信我可以通过首先创建一个中间字典来完成此操作,其中 所有 更改被串联起来:
# 3
{
'price': [[0, 490], [490, 530]],
'status': [[10, 1], [1, 7], [7, 1]],
'location': [[None, 'Calgary']]
}
然后提取每个字典元素的第一个列表元素的第一个列表元素,以及每个字典元素的最后一个列表元素的最后一个列表元素。
什么是让 #1
看起来像 #3
的干净且 Pythonic 的方法?
在显示的示例数据中,更改按时间倒序列出。只需遍历构建一组合并字段的列表:每个重复的字段都会更新 'old' 值,'new' 来自第一个更改。
changes = [
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
squashed = {}
for delta in changes:
for field, values in delta.items():
if field in squashed:
squashed[field][0] = values[0]
else:
squashed[field] = values
产生以下结果:
In [7]: print(squashed)
{'status': [10, 1], 'location': [None, 'Calgary'], 'price': [0, 530]}
您可以直接转到#2。在遍历 #1 时,如果密钥是新的,则创建一个新条目,并且只更新结束状态:
l = [
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
l.reverse()
squashed = {}
for x in l:
for k,v in x.items():
squashed.setdefault(k, [v[0],v[1]])
squashed[k][1] = v[1]
print squashed
dict.setdefault()
可能对您有用:
from pprint import pprint
one = [
{
'price': [490, 530]
},
{
'status': [7, 1],
},
{
'status': [1, 7],
},
{
'status': [10, 1],
'price': [0, 490],
'location': [None, 'Calgary']
}
]
two = {}
for d in one:
for k,v in d.items():
two.setdefault(k, v)[0] = v[0]
pprint(two)
结果:
{'location': [None, 'Calgary'], 'price': [0, 530], 'status': [10, 1]}
考虑更新列表:
crunch=lambda d,u: dict(d.items()+[(k, [u[k][0], d.get(k, u[k])[1]]) for k in u])
reduce(crunch, l)
这给你:
{'location': [None, 'Calgary'], 'price': [0, 530], 'status': [10, 1]}
因此,reduce 函数的第一个参数是一个函数,它接收按以下方式从列表中获取的一对参数:
l = [ 0, 1, 2, 3 ]
reduce( f, l ) == f( f ( f( f(0, 1), 2), 3)
通过这种方式,lambda 函数接收一个增量构建的字典作为第一个参数 (d),并通过迭代 u 中的更新来构建一个新的更新字典。
lambda 函数变得过于复杂,因为更新方法不是 return 字典而是 None,所以它正在构建一个新字典,而只是为了能够 return它。
您可以将 lambda 替换为实际函数,作为更清晰的替代方案,可以轻松 return 更新字典:
def crunch(dic, updates):
dic.update(
{ k: [updates[k][0], dic.get(k, updates[k])[1]] for k in updates }
)
return dic # gonna be the input of the next iteration
然后做:
reduce(crunch, l)
字典的 get 方法 return如果 k 存在则为项目值,如果不存在则将第二个参数作为默认值,因此它不需要 defaultdict 或 setdefault。