python natsort: How to sort alphabetically first with an associated version number within a list of dictionaries

49 Views Asked by At

Let's say I have this list:

my_list = [
{'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}, 
{'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}
]

I want the resulting list to be sorted first by what precedes the @ and then by the version numbers.

Doing this: natsort.natsorted(my_list, key=itemgetter(*['id']), reverse=True)

I get the following result:

[{'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'}]

How do I get natsort to work so that all the items for "Air" come before all the items for "Bear", while still sorting also by version (as it's currently doing correctly)?

2

There are 2 best solutions below

11
chepner On BEST ANSWER

I would use a custom comparison function to reverse the comparison only on the version numbers, using natsort.natsort_keygen to make a function that will compare versions numbers correctly.

from operator import methodcaller, itemgetter

my_list = [
{'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'},
{'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}, {'id': '[email protected]'}
]


vkey = natsort.natsort_keygen()

# Python 2's three-way comparison function:
#  x < y returns negative
#  x == y returns zero
#  x > y returns positive
#
# To reverse the comparison, swap the arguments.
def cmp(x, y):
    return -1 if x < y else 0 if x == y else 1



def cmp_str(x, y):
    splitter = operator.methodcaller('split', '@')
    getter = operator.itemgetter('id')
    x1, x2 = splitter(getter(x))
    y1, y2 = splitter(getter(y))
    return cmp(x1, y1) \
           or cmp(vkey(y2), vkey(x2))

for x in sorted(my_list, key=functools.cmp_to_key(cmp_str)):
    print(x)

Output:

{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
3
no comment On

Take the natsorted list and sort it solely based on what precedes the @. The sorting is stable, so the sorting of versions is preserved.

ds = [{'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'},
 {'id': '[email protected]'}]

ds.sort(key=lambda d: d['id'].split('@')[0])

for d in ds:
    print(d)

Output (Attempt This Online!):

{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}
{'id': '[email protected]'}