ac2001
ac2001

Reputation: 736

Sort list of Dict By Multiple Keys, Including List

I would like to sort this list of dicts by a list key and then by date. I am trying to sort the dicts by 'label' according the label_order and then by descending 'date'.

label_order = [3, 4, 2, 1]

data = [
    {'label': 1, 'data': 5, 'date': datetime(2018, 12, 31)},
    {'label': 3, 'data': 2, 'date': datetime(2017, 12, 31)},
    {'label': 3, 'data': 1, 'date': datetime(2018, 12, 31)},
    {'label': 4, 'data': 3, 'date': datetime(2018, 12, 31)},
    {'label': 4, 'data': 4, 'date': datetime(2018, 12, 25)},
]

After sorting would look like this:

data = [
    {'label': 3, 'data': 1, 'date': datetime(2018, 12, 31)},
    {'label': 3, 'data': 2, 'date': datetime(2017, 12, 31)},
    {'label': 4, 'data': 3, 'date': datetime(2018, 12, 31)},
    {'label': 4, 'data': 4, 'date': datetime(2018, 12, 25)},
    {'label': 1, 'data': 5, 'date': datetime(2018, 12, 31)},
]

I've tried lambda expressions and itemgetter, but I am having difficulty combining the right strategies for the sort key. Maybe it is just trying to do too much at one time.

Any help or direction would be appreciated.

Upvotes: 2

Views: 715

Answers (2)

Kirk Strauser
Kirk Strauser

Reputation: 30957

It's a little tricky to sort dates in reverse order. Instead, let's use the negative of the label's index so they're sorted in descending order. Then we can reverse the sorting and get the results in the order we actually want!

from datetime import datetime

label_order = [3, 4, 2, 1]

data = [
    {'label': 1, 'data': 5, 'date': datetime(2018, 12, 31)},
    {'label': 3, 'data': 2, 'date': datetime(2017, 12, 31)},
    {'label': 3, 'data': 1, 'date': datetime(2018, 12, 31)},
    {'label': 4, 'data': 3, 'date': datetime(2018, 12, 31)},
    {'label': 4, 'data': 4, 'date': datetime(2018, 12, 25)},
]

def descending_sort_key(item):
    return -label_order.index(item['label']), item['date']

data.sort(key=descending_sort_key, reverse=True)

Voila - no date math or other trickery.

Upvotes: 1

blhsing
blhsing

Reputation: 107124

A more efficient approach is to build a dict that maps items in label_order to indices, so that you can use the indices as keys when performing the sort:

keys = {n: i for i, n in enumerate(label_order)}
sorted(data, key=lambda d: (-keys[d['label']], d['date']), reverse=True)

This returns:

[{'label': 3, 'data': 1, 'date': datetime(2018, 12, 31)},
 {'label': 3, 'data': 2, 'date': datetime(2017, 12, 31)},
 {'label': 4, 'data': 3, 'date': datetime(2018, 12, 31)},
 {'label': 4, 'data': 4, 'date': datetime(2018, 12, 25)},
 {'label': 1, 'data': 5, 'date': datetime(2018, 12, 31)}]

Upvotes: 2

Related Questions