pythonnatsort

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


Let's say I have this list:

my_list = [
{'id': 'Bear@1.0.1'}, {'id': 'Bear@5.5.1'}, {'id': 'Bear@10.0.9'}, {'id': 'Bear@5.1'}, 
{'id': 'Air@7.6'}, {'id': 'Air@8.7'}, {'id': 'Air@10.0.0'}, {'id': 'Air@1.0'}
]

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': 'Bear@10.0.9'},
 {'id': 'Bear@5.5.1'},
 {'id': 'Bear@5.1'},
 {'id': 'Bear@1.0.1'},
 {'id': 'Air@10.0.0'},
 {'id': 'Air@8.7'},
 {'id': 'Air@7.6'},
 {'id': 'Air@1.0'}]

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)?


Solution

  • 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': 'Bear@1.0.1'}, {'id': 'Bear@5.5.1'}, {'id': 'Bear@10.0.9'}, {'id': 'Bear@5.1'},
    {'id': 'Air@7.6'}, {'id': 'Air@8.7'}, {'id': 'Air@10.0.0'}, {'id': 'Air@1.0'}
    ]
    
    
    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': 'Air@10.0.0'}
    {'id': 'Air@8.7'}
    {'id': 'Air@7.6'}
    {'id': 'Air@1.0'}
    {'id': 'Bear@10.0.9'}
    {'id': 'Bear@5.5.1'}
    {'id': 'Bear@5.1'}
    {'id': 'Bear@1.0.1'}