How to Sort a List with Two Keys But One in Reverse Order

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



Leave a reply



Submit