我如何“压制”一个词典列表?

时间:2016-06-14 23:20:43

标签: python python-3.x data-structures django-auditlog

对标题不清楚道歉,但我不确定如何描述我正在尝试的操作。

django-auditlog在格式为{'field_name': [old_value, new_value]}的Django模型中生成跟踪字段的“差异”,在更改时跟踪数据库中的字段。因此,我的数据库中特定行上的这些差异列表,首先按最近的差异排序,可能如下所示:

# 1
[
  {
    'price': [490, 530]
  },
  {
    'status': [7, 1],
  },
  {
    'status': [1, 7],
  },
  {
    'status': [10, 1],
    'price': [0, 490],
    'location': [None, 'Calgary']
  }
]

我想像在Git中那样“压缩”这段历史:获取字段的第一个值和字段的最新值,并删除所有中间值。所以在上面的例子中,我想要以下输出:

# 2
{
  'price': [0, 530],
  'status': [10, 1],
  'location': [None, 'Calgary']
}

请注意,多个'status''price'更改已被压缩为单个旧/新对。

我相信我可以通过首先创建一个中间词典来实现这一点,其中 all 连接的变化是连接的:

# 3
{
  'price': [[0, 490], [490, 530]],
  'status': [[10, 1], [1, 7], [7, 1]],
  'location': [[None, 'Calgary']]
}

然后提取每个字典元素的第一个list元素的第一个list元素,以及每个字典元素的最后一个list元素的最后一个list元素。

#1看起来像#3的干净和Pythonic方法是什么?

4 个答案:

答案 0 :(得分:2)

在显示的示例数据中,更改按反向时间顺序列出。只需逐步完成列表构建一组合并字段:每个重复字段都会更新“旧”值,而“新”值将来自第一次更改。

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]}

答案 1 :(得分:2)

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]}

答案 2 :(得分:2)

考虑更新列表:

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函数变得过于复杂,因为update方法不返回字典而是None,因此它正在构建一个新字典,而只是为了能够返回它。

您可以将lambda替换为实际函数,作为更清晰的替代方案,可以轻松返回更新后的字典:

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)

如果k存在则字典的get方法返回项值,如果不存在则返回第二个参数作为默认值,因此它不需要defaultdict或setdefault。

答案 3 :(得分:0)

你可以直接进入#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