How to sort a list with two keys but one in reverse order?
Two keys will be used when we need to sort a list with two constraints: one in ascending order and the other in descending, in the same list or any
In your example,
sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]))
you can sort entire list only in one order.
You can try these and check what's happening:
sortedList = sorted(myList, key = lambda y: (y[0].lower(), -y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), -y[1]))
Python sorted two keys TWO orders
If your first element in the tuple is an integer, you can sort by its negative value:
sorted(theList, key=lambda (num, letter): (-num, letter))
Sort a list by multiple attributes?
A key can be a function that returns a tuple:
s = sorted(s, key = lambda x: (x[1], x[2]))
Or you can achieve the same using itemgetter
(which is faster and avoids a Python function call):
import operator
s = sorted(s, key = operator.itemgetter(1, 2))
And notice that here you can use sort
instead of using sorted
and then reassigning:
s.sort(key = operator.itemgetter(1, 2))
Sort by 2 keys but reverse only first key
assuming your field is stored as string but contains only ids which contain only digits. Then you can do an inplace sort by converting the id to an int for the purpose of the sort, this wont change the underlying type of the data, just merely for deciding the sort order.
data = """1282 2019-12-19 12:09:28
1281 2019-12-19 12:09:28
1280 2019-12-19 12:09:28
1279 2019-12-19 12:09:27
1278 2019-12-19 12:09:27
1277 2019-12-19 12:09:27
1275 2019-12-19 12:09:27"""
s = []
for line in data.splitlines():
ID, *date = line.split()
s.append((ID, " ".join(date)))
print("###BEFORE###")
for item in s:
print(item[0], item[1])
s.sort(key=lambda x: (x[1], -int(x[0])), reverse=True)
print("###AFTER###")
for item in s:
print(item[0], item[1])
print(f'type of {s[0][0]} is still {type(s[0][0])}')
OUTPUT
###BEFORE###
1282 2019-12-19 12:09:28
1281 2019-12-19 12:09:28
1280 2019-12-19 12:09:28
1279 2019-12-19 12:09:27
1278 2019-12-19 12:09:27
1277 2019-12-19 12:09:27
1275 2019-12-19 12:09:27
###AFTER###
1280 2019-12-19 12:09:28
1281 2019-12-19 12:09:28
1282 2019-12-19 12:09:28
1275 2019-12-19 12:09:27
1277 2019-12-19 12:09:27
1278 2019-12-19 12:09:27
1279 2019-12-19 12:09:27
type of 1280 is still <class 'str'>
Sort by multiple keys using different orderings
There is no built-in way to handle this. For the general case, you must sort twice: first by the secondary sort, then by the primary sort. As @Mark Ransom mentioned in his comment, in many cases the variables are numeric, and so you can use the negative value to flip the ordering.
If you know the type of the variable you're trying to sort on and how to work with it, you could also write a key function that returns a decreasing value for increasing keys. See this thread for an example for strings. (Basically, you take the negative of the ASCII numerical value of the characters.)
In Python 2, you could also use a cmp
function instead of a key, but this will likely make the sort slower. Whether it will make it too slow depends on how big and unsorted the list is. In Python 3, the cmp
argument is gone, but as @Mark Ransom notes you could use cmp_to_key
.
How to sort objects by multiple keys?
This answer works for any kind of column in the dictionary -- the negated column need not be a number.
def multikeysort(items, columns):
from operator import itemgetter
comparers = [((itemgetter(col[1:].strip()), -1) if col.startswith('-') else
(itemgetter(col.strip()), 1)) for col in columns]
def comparer(left, right):
for fn, mult in comparers:
result = cmp(fn(left), fn(right))
if result:
return mult * result
else:
return 0
return sorted(items, cmp=comparer)
You can call it like this:
b = [{u'TOT_PTS_Misc': u'Utley, Alex', u'Total_Points': 96.0},
{u'TOT_PTS_Misc': u'Russo, Brandon', u'Total_Points': 96.0},
{u'TOT_PTS_Misc': u'Chappell, Justin', u'Total_Points': 96.0},
{u'TOT_PTS_Misc': u'Foster, Toney', u'Total_Points': 80.0},
{u'TOT_PTS_Misc': u'Lawson, Roman', u'Total_Points': 80.0},
{u'TOT_PTS_Misc': u'Lempke, Sam', u'Total_Points': 80.0},
{u'TOT_PTS_Misc': u'Gnezda, Alex', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Kirks, Damien', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Worden, Tom', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Korecz, Mike', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Swartz, Brian', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Burgess, Randy', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Smugala, Ryan', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Harmon, Gary', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Blasinsky, Scott', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Carter III, Laymon', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Coleman, Johnathan', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Venditti, Nick', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Blackwell, Devon', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Kovach, Alex', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Bolden, Antonio', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Smith, Ryan', u'Total_Points': 60.0}]
a = multikeysort(b, ['-Total_Points', 'TOT_PTS_Misc'])
for item in a:
print item
Try it with either column negated. You will see the sort order reverse.
Next: change it so it does not use extra class....
2016-01-17
Taking my inspiration from this answer What is the best way to get the first item from an iterable matching a condition?, I shortened the code:
from operator import itemgetter as i
def multikeysort(items, columns):
comparers = [
((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1))
for col in columns
]
def comparer(left, right):
comparer_iter = (
cmp(fn(left), fn(right)) * mult
for fn, mult in comparers
)
return next((result for result in comparer_iter if result), 0)
return sorted(items, cmp=comparer)
In case you like your code terse.
Later 2016-01-17
This works with python3 (which eliminated the cmp
argument to sort
):
from operator import itemgetter as i
from functools import cmp_to_key
def cmp(x, y):
"""
Replacement for built-in function cmp that was removed in Python 3
Compare the two objects x and y and return an integer according to
the outcome. The return value is negative if x < y, zero if x == y
and strictly positive if x > y.
https://portingguide.readthedocs.io/en/latest/comparisons.html#the-cmp-function
"""
return (x > y) - (x < y)
def multikeysort(items, columns):
comparers = [
((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1))
for col in columns
]
def comparer(left, right):
comparer_iter = (
cmp(fn(left), fn(right)) * mult
for fn, mult in comparers
)
return next((result for result in comparer_iter if result), 0)
return sorted(items, key=cmp_to_key(comparer))
Inspired by this answer How should I do custom sort in Python 3?
Sort python dict by two values
sorted(d, key=lambda x: (d[x][2], -len(d[x][0])))
Explanation:
Iteration over dictionaries returns keys. So, sorted(d, ...)
will return keys sorted by the key
criteria
key
lambda generates tuples that will be used for comparison in sort. Tuples are compared using first element, then second, etc. If tuple is used as a key, it will sort using the first element (x[2]
), then second (length of the first string), etc.
To reverse order on the length, but not on the numeric value, the length is negated
sorted on basis of two keys, descending order sort for first and ascending for second
negate the second key:
lst = [(1,2), (3,2), (1,3), (3,4)]
print(sorted(lst,key=lambda x: (x[0],- x[1]),reverse=True))
[(3, 2), (3, 4), (1, 2), (1, 3)]
Or the first and remove the reverse:
print(sorted(lst,key=lambda x: -x[0])
[(3, 2), (3, 4), (1, 2), (1, 3)]
sorting from lowest to highest will put the lower second values first but using -x[0]
will put the higher first elements first.
Based on your edit you need to cast x[0]
to int and parse the times into datetime objects:
from datetime import datetime
lst = [('1', '3:00 PM'), ('1', '3:00 AM'), ('2', '1:00 AM'), ('2', '2:00 AM')]
print(sorted(lst,key=lambda x: (-int(x[0]), datetime.strptime(x[1], "%H:%M %p"))))
[('2', '1:00 AM'), ('2', '2:00 AM'), ('1', '3:00 PM'), ('1', '3:00 AM')]
Sort list of dicts by two keys
You can sort by a tuple:
sorted(l, key=lambda k: (float(k['score']), k['id']), reverse=True)
This will sort by score
descending, then id
descending. Note that since score
is a string value, it needs to be converted to float
for comparison.
[
{'score': '2.0', 'id': 686, 'factors': [2.0, 2.25, 2.75, 1.5, 2.25]},
{'score': '2.0', 'id': 55, 'factors': [1.5, 3.0, 2.5, 1.5, 1.5]},
{'score': '1.9', 'id': 863, 'factors': [1.5, 3.0, 1.5, 2.5, 1.5]},
{'score': '1.9', 'id': 756, 'factors': [1.25, 2.25, 2.5, 2.0, 1.75]}
]
To sort by id
ascending, use -k['id']
(sorting negated numbers descending is equivalent to sorting the non-negated numbers ascending):
sorted(l, key=lambda k: (float(k['score']), -k['id']), reverse=True)
[
{'score': '2.0', 'id': 55, 'factors': [1.5, 3.0, 2.5, 1.5, 1.5]},
{'score': '2.0', 'id': 686, 'factors': [2.0, 2.25, 2.75, 1.5, 2.25]},
{'score': '1.9', 'id': 756, 'factors': [1.25, 2.25, 2.5, 2.0, 1.75]},
{'score': '1.9', 'id': 863, 'factors': [1.5, 3.0, 1.5, 2.5, 1.5]}
]
Related Topics
Google Colab: How to Read Data from My Google Drive
How to Profile Python Code Line-By-Line
How to Change Default Anaconda Python Environment
Timeout for Python Requests.Get Entire Response
Multiple Variables in a 'With' Statement
Accessing Mp3 Metadata with Python
How to Dynamically Compose an or Query Filter in Django
How to Activate a Virtualenv Inside Pycharm's Terminal
How to Read and Write Ini File with Python3
How to Format a String Using a Dictionary in Python-3.X
Difference Between Len() and ._Len_()
Print to Standard Printer from Python
Search for String in All Pandas Dataframe Columns and Filter
Problems with Pip Install Numpy - Runtimeerror: Broken Toolchain: Cannot Link a Simple C Program
Reloading Module Giving Nameerror: Name 'Reload' Is Not Defined