Python, sort dict based on external list

Question:

I have to sort a dict like:

jobs = {'elem_05': {'id': 'fifth'},
        'elem_03': {'id': 'third'},
        'elem_01': {'id': 'first'},
        'elem_00': {'id': 'zeroth'},
        'elem_04': {'id': 'fourth'},
        'elem_02': {'id': 'second'}}

based on the "id" elements, whose order can be found in a list:

sorting_list = ['zeroth', 'first', 'second', 'third', 'fourth', 'fifth']

The trivial way to solve the problem is to use:

tmp = {}
for x in sorting_list:
    for k, v in jobs.items():
        if v["id"] == x:
            tmp.update({k: v})

but I was trying to figure out a more efficient and pythonic way.
I’ve been trying sorted and lambda functions as key, but I’m not familiar with that yet, so I was unsuccessful so far.

Asked By: Carlo

||

Answers:

There is a way to sort the dict using lambda as a sorting key:

jobs = {'elem_05': {'id': 'fifth'},
    'elem_03': {'id': 'third'},
    'elem_01': {'id': 'first'},
    'elem_00': {'id': 'zeroth'},
    'elem_04': {'id': 'fourth'},
    'elem_02': {'id': 'second'}}

sorting_list = ['zeroth', 'first', 'second', 'third', 'fourth', 'fifth']

sorted_jobs = dict(sorted(jobs.items(), key=lambda x: sorting_list.index(x[1]['id'])))

print(sorted_jobs)

This outputs

{'elem_00': {'id': 'zeroth'}, 'elem_01': {'id': 'first'}, 'elem_02': {'id': 'second'}, 'elem_03': {'id': 'third'}, 'elem_04': {'id': 'fourth'}, 'elem_05': {'id': 'fifth'}}

I have a feeling the sorted expression could be cleaner but I didn’t get it to work any other way.

Answered By: LTJ

The sorted function costs O(n log n) in average time complexity. For a linear time complexity you can instead create a reverse mapping that maps each ID to the corresponding dict entry:

mapping = {d['id']: (k, d) for k, d in jobs.items()}

so that you can then construct a new dict by mapping sorting_list with the ID mapping above:

dict(map(mapping.get, sorting_list))

which, with your sample input, returns:

{'elem_00': {'id': 'zeroth'}, 'elem_01': {'id': 'first'}, 'elem_02': {'id': 'second'}, 'elem_03': {'id': 'third'}, 'elem_04': {'id': 'fourth'}, 'elem_05': {'id': 'fifth'}}

Demo: https://replit.com/@blhsing/WorseChartreuseFonts

Answered By: blhsing

You can use OrderedDict:

from collections import OrderedDict

sorted_jobs = OrderedDict([(el, jobs[key]['id']) for el, key in zip(sorting_list, jobs.keys())])

This creates an OrderedDict object which is pretty similar to dict, and can be converted to dict using dict(sorted_jobs).

Answered By: jurel

Similar to what is already posted, but with error checking in case id doesn’t appear in sorting_list

sorting_list = ['zeroth', 'first', 'second', 'third', 'fourth', 'fifth']

jobs = {'elem_05': {'id': 'fifth'},
        'elem_03': {'id': 'third'},
        'elem_01': {'id': 'first'},
        'elem_00': {'id': 'zeroth'},
        'elem_04': {'id': 'fourth'},
        'elem_02': {'id': 'second'}}


def custom_order(item):
    try:
        return sorting_list.index(item[1]["id"])
    except ValueError:
        return len(sorting_list)


jobs_sorted = {k: v for k, v in sorted(jobs.items(), key=custom_order)}
print(jobs_sorted)
Answered By: Tzane

I would use a dictionary as key for sorted:

order = {k:i for i,k in enumerate(sorting_list)}
# {'zeroth': 0, 'first': 1, 'second': 2, 'third': 3, 'fourth': 4, 'fifth': 5}

out = dict(sorted(jobs.items(), key=lambda x: order.get(x[1].get('id'))))

output:

{'elem_00': {'id': 'zeroth'},
 'elem_01': {'id': 'first'},
 'elem_02': {'id': 'second'},
 'elem_03': {'id': 'third'},
 'elem_04': {'id': 'fourth'},
 'elem_05': {'id': 'fifth'}}
Answered By: mozway
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.