7

I need to sort a list of dictionaries by a specific value. Unfortunately, some values are None and the sorting does not work in Python 3 because of the fact that it does not support comparison of None with not None values. I need to retain the None values as well and place them as lowest values in the new sorted list.

The code:

import operator

list_of_dicts_with_nones = [
    {"value": 1, "other_value": 4},
    {"value": 2, "other_value": 3},
    {"value": 3, "other_value": 2},
    {"value": 4, "other_value": 1},
    {"value": None, "other_value": 42},
    {"value": None, "other_value": 9001}
]

# sort by first value but put the None values at the end
new_sorted_list = sorted(
    (some_dict for some_dict in list_of_dicts_with_nones),
    key=operator.itemgetter("value"), reverse=True
)

print(new_sorted_list)

What I get in Python 3.6.1:

Traceback (most recent call last):
  File "/home/bilan/PycharmProjects/py3_tests/py_3_sorting.py", line 15, in <module>
    key=operator.itemgetter("value"), reverse=True
TypeError: '<' not supported between instances of 'NoneType' and 'NoneType'

What I need (this works in Python 2.7):

[{'value': 4, 'other_value': 1}, {'value': 3, 'other_value': 2}, {'value': 2, 'other_value': 3}, {'value': 1, 'other_value': 4}, {'value': None, 'other_value': 42}, {'value': None, 'other_value': 10001}]

Yes, I know there are similar questions to this one, but they do not deal with this particular use case with operator.itemgetter:

A number smaller than negative infinity in python?

Is everything greater than None?

Comparing None with built-in types using arithmetic operators?

I can recreate the sorting behavior of Python 2 in Python 3 when there are no dictionaries involved. But I don't see a way to do this with the operator.

Ivan Bilan
  • 2,379
  • 5
  • 38
  • 58

4 Answers4

14

For Python 3: As mentioned here you can do something like this in your case:

L = [  # I mixed them to shown the sorting
     {"value": 1, "other_value": 4},
     {"value": 2, "other_value": 3},
     {"value": None, "other_value": 2},
     {"value": 4, "other_value": 1},
     {"value": None, "other_value": 42},
     {"value": 3, "other_value": 9001}
    ]

L.sort(key= lambda x: (x['value'] is not None, x['value']), reverse=True)

print(L)
>>>[{'value': 4, 'other_value': 1}, {'value': 3, 'other_value': 9001}, {'value': 2, 'other_value': 3}, {'value': 1, 'other_value': 4}, {'value': None, 'other_value': 2}, {'value': None, 'other_value': 42}]
binaryEcon
  • 380
  • 4
  • 12
5

I found a way to do it by using lambda key on value. This is the code:

L = [  # I mixed them to shown the sorting
    {"value": 1, "other_value": 4},
    {"value": 2, "other_value": 3},
    {"value": None, "other_value": 2},
    {"value": 4, "other_value": 1},
    {"value": None, "other_value": 42},
    {"value": 3, "other_value": 9001}
]

def weighted(nb):
    if nb is None:
        return -float('inf')
    else:
        return nb

L.sort(key=lambda x:weighted(x["value"]), reverse=True)
print(L) # => return the expected output in python 3.6

There is probably another way to write the "weighted" function shorter but it works. The idea is just to return -infinite for None value and then sort by the value.

I hope it helps,

Nicolas M.
  • 1,472
  • 1
  • 13
  • 26
1

maxsize is the maximum size of Python data structures, e.g. how many elements can be in a list. This is not technically the smallest possible integer (since Python 3 has unbounded integer values) but it should suffice in normal use-cases.

import operator 
import sys

my_list = [
    {"value": 1, "other_value": 4},
    {"value": 2, "other_value": 3},
    {"value": 3, "other_value": 2},
    {"value": 4, "other_value": 1},
    {"value": None, "other_value": 42},
    {"value": None, "other_value": 9001}
]

def key(e):
    v = e["value"]
    return -sys.maxsize if v is None else v

new_sorted_list = sorted((my_list ),
    key=key, reverse=True
)

print(new_sorted_list)

gives,

[{'value': 4, 'other_value': 1}, {'value': 3, 'other_value': 2}, {'value': 2, 'other_value': 3}, {'value': 1, 'other_value': 4}, {'value': None, 'other_value': 42}, {'value': None, 'other_value': 9001}]

[Program finished]
Subham
  • 397
  • 1
  • 6
  • 14
0

I would filter out None values first and then do the sorting the usual way:

my_list = [d for d in my_list if all(d.values())]
Endogen
  • 589
  • 2
  • 12
  • 24