I was looking through some popular questions on SO, and one of them was How do I sort a dictionary by value?. Because Python 3.6 now gives us dict
s that maintain their structure, I thought I'd give this question a go. Here is my answer.
My hope is that this function eases the sorting of an iterable (tuple, list, or dict). In the latter case, you can sort either on keys or values, and it can take numeric comparison into account.
When you try using sorted
on an iterable that holds e.g. strings as well as ints, it will fail. Of course you can force string comparison with str(). However, in some cases you want to do actual numeric comparison where 12
is smaller than 20
(which is not the case in string comparison). So I came up with the following. When you want explicit numeric comparison you can use the flag num_as_num
which will try to do explicit numeric sorting by trying to convert all values to floats. If that succeeds, it will do numeric sorting, otherwise it'll resort to string comparison.
Looking forward to see what can be improved, efficiency wise as well as code structure wise.
def sort_iterable(iterable, sort_on=None, reverse=False, num_as_num=False):
def _sort(i):
try:
if num_as_num:
if i is None:
_sorted = sorted(iterable, key=lambda v: float(v), reverse=reverse)
else:
_sorted = dict(sorted(iterable.items(), key=lambda v: float(v[i]), reverse=reverse))
else:
raise TypeError
except (TypeError, ValueError):
if i is None:
_sorted = sorted(iterable, key=lambda v: str(v), reverse=reverse)
else:
_sorted = dict(sorted(iterable.items(), key=lambda v: str(v[i]), reverse=reverse))
return _sorted
if isinstance(iterable, list):
sorted_list = _sort(None)
return sorted_list
elif isinstance(iterable, tuple):
sorted_list = tuple(_sort(None))
return sorted_list
elif isinstance(iterable, dict):
if sort_on == 'keys':
sorted_dict = _sort(0)
return sorted_dict
elif sort_on == 'values':
sorted_dict = _sort(1)
return sorted_dict
elif sort_on is not None:
raise ValueError(f"Unexpected value {sort_on} for sort_on. When sorting a dict, use key or values")
else:
raise TypeError(f"Unexpected type {type(iterable)} for iterable. Expected a list, tuple, or dict")
Update following the answers:
- Changed lambda expressions to just the
float
orstr
function where applicable. - Changed the try-except logic and how it interacts with conditionals.
- Raise an exception when using
sort_on=None
when giving adict
. - Removed intermediary variable assignments
I am not going into backwards-or-forwards compatibility issues. If anything, it's more a fun project to improve my programming.
def sort_iterable(iterable, sort_on=None, reverse=False, num_as_num=False):
def _sort(i):
if num_as_num:
try:
if i is None:
return sorted(iterable, key=float, reverse=reverse)
else:
return dict(sorted(iterable.items(), key=lambda v: float(v[i]), reverse=reverse))
except TypeError:
raise TypeError("Tried parsing as float but could not. Only use num_as_num when all items that need "
"to be sorted can be converted to float")
else:
if i is None:
return sorted(iterable, key=str, reverse=reverse)
else:
return dict(sorted(iterable.items(), key=lambda v: str(v[i]), reverse=reverse))
if isinstance(iterable, list):
return _sort(None)
elif isinstance(iterable, tuple):
return tuple(_sort(None))
elif isinstance(iterable, dict):
if sort_on.lower() == 'keys':
return _sort(0)
elif sort_on.lower() == 'values':
return _sort(1)
else:
raise ValueError(f"Unexpected value {sort_on} for sort_on. When sorting a dict, use keys or values")
else:
raise TypeError(f"Unexpected type {type(iterable)} for iterable. Expected a list, tuple, or dict")