ssdecontrol ssdecontrol - 5 months ago 25
Python Question

How can I "squash" a list of dictionaries?

Apologies for an unclear title, but I'm not sure how else to describe the operation I'm trying to do.

django-auditlog produces "diffs" of tracked fields in Django models of the format

{'field_name': [old_value, new_value]}
, that keep track of fields in the database when they are changed. So a list of these diffs on a particular row in my database, sorted with the most recent diffs first, might look like the following:

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


I would like to "squash" this history like I would in Git: taking the very first value of a field and the most recent value of a field, and dropping all the intermediate values. So in the above example, I'd like the following output:

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


Note that the multiple
'status'
and
'price'
changes have been squashed down to a single old/new pair.

I believe I could accomplish this by first creating an intermediate dictionary in which all the changes are concatenated:

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


and then extracting the first list element of the first list element of each dictionary element, and the last list element of the last list element of each dictionary element.

What is a clean and Pythonic way to get
#1
to look like
#3
?

Answer

In the example data shown, changes are listed in reverse chronological order. Simply step through the list building up a set of merged fields: each repeated field updates the 'old' value, with 'new' coming from the very first change.

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

yields the following:

In [7]: print(squashed)
{'status': [10, 1], 'location': [None, 'Calgary'], 'price': [0, 530]}
Comments