This article has a nice rundown on various techniques for doing this. If your requirements are simpler than "full bidirectional multikey", take a look. It's clear the accepted answer and the blog post I just referenced influenced each other in some way, though I don't know which order.
In case the link dies here's a very quick synopsis of examples not covered above:
from operator import itemgetter
mylist = sorted(mylist, key=itemgetter('name', 'age'))
mylist = sorted(mylist, key=lambda k: (k['name'].lower(), k['age']))
mylist = sorted(mylist, key=lambda k: (k['name'].lower(), -k['age']))
Answer from Scott Stafford on Stack OverflowThis article has a nice rundown on various techniques for doing this. If your requirements are simpler than "full bidirectional multikey", take a look. It's clear the accepted answer and the blog post I just referenced influenced each other in some way, though I don't know which order.
In case the link dies here's a very quick synopsis of examples not covered above:
from operator import itemgetter
mylist = sorted(mylist, key=itemgetter('name', 'age'))
mylist = sorted(mylist, key=lambda k: (k['name'].lower(), k['age']))
mylist = sorted(mylist, key=lambda k: (k['name'].lower(), -k['age']))
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?
I'm trying to write a Python function that sorts a list of dictionaries by multiple keys, but I keep running into issues with the ordering and index positions. Here's an example of what I'm working with:
```
[
{"name": "John", "age": 30, "city": "New York"},
{"name": "Alice", "age": 25, "city": "Chicago"},
{"name": "Bob", "age": 40, "city": "San Francisco"}
]
```
I want to sort this list by "name" first, and then by "age". However, when I use the `sorted()` function with a custom key, it seems to be treating all keys as if they were equal. For example, if I'm sorting by "name" and "age", but there are duplicates in "name" (e.g. two people named "Alice"), it will treat those as if they're equal.
Does anyone know of a way to achieve this in Python? Or is there a better data structure I should be using for this type of task?
I've tried using the `sorted()` function with a custom key, but like I said, it doesn't seem to work as expected. I've also looked into using `numpy` or `pandas`, but those seem to overcomplicate things for what I need.
Edit: I've been experimenting with different sorting methods, and I've come across a solution that uses the `functools.cmp_to_key()` function to convert my comparison function to a key function. However, I'm still having issues with getting the desired output.
So say this is my dictionary: {'John Adams': ('111223333', 'A', 91.0), 'Willy Smith Jr.': ('222114444', 'C', 77.55), 'Phil Jordan': ('777886666', 'F', 59.5)} and i want to sort it by the third value of each key (eg the 91.0 for John Adams). How would I go about doing that?
Use a lambda as your sort function.
# lambda r: r[1][2]
# r == ('John Adams', ('111223333', 'A', 91.0))
# r[1] == ('111223333', 'A', 91.0)
# r[1][2] == 91.0
#
d = {
'John Adams': ('111223333', 'A', 91.0),
'Willy Smith Jr.': ('222114444', 'C', 77.55),
'Phil Jordan': ('777886666', 'F', 59.5)
}
for key, value in sorted(d.items(), key=lambda r: r[1][2]):
print(key, value)
Produces:
('Phil Jordan', ('777886666', 'F', 59.5))
('Willy Smith Jr.', ('222114444', 'C', 77.55))
('John Adams', ('111223333', 'A', 91.0))
By the way, if you end up with multiple entries sharing the same sort value, you can build a compound sort (secondary, tertiary, etc). Your lambda should just return a tuple: lambda r: (r[1][2], r[0]), which would sort by that last value, and then by name if multiple values equal each other.
Here's without lambda:
def key_func(key_value_tuple):
name = key_value_tuple[0]
long_str_num, a_to_f, number = key_value_tuple[1]
return (number, name)
d = {
'John Adams': ('111223333', 'A', 91.0),
'Willy Smith Jr.': ('222114444', 'C', 77.55),
'Phil Jordan': ('777886666', 'F', 59.5)
}
for key, value in sorted(d.items(), key=key_func):
print(key, value)
Edit: Added non-lambda option.
Here's an extension of u/totallygeek's non-lambda solution, using a more advanced Python feature:
d = {'John Adams': ('111223333', 'A', 91.0), 'Willy Smith Jr.': ('222114444', 'C', 77.55),
'Phil Jordan': ('777886666', 'F', 59.5)}
def sortindex(index):
def key_func(item):
key, value = item
return (value[index], key)
return key_func
for key, value in sorted(d.items(), key=sortindex(2)):
print(key, value)
Here, key_func is wrapped inside another function, sortindex, which returns key_func. In other words, the code above does the same thing as this
def key_func(item):
key, value = item
return (value[2], key)
for key, value in sorted(d.items(), key=key_func):
print(key, value)
But notice that the sortindex function allows us to keep the tuple index as a free variable, so that we can also sort on sortindex(0) or sortindex(1).
This technique of wrapping one function inside another is called a closure.
The sorted() function takes a key= parameter
newlist = sorted(list_to_be_sorted, key=lambda d: d['name'])
Alternatively, you can use operator.itemgetter instead of defining the function yourself
from operator import itemgetter
newlist = sorted(list_to_be_sorted, key=itemgetter('name'))
For completeness, add reverse=True to sort in descending order
newlist = sorted(list_to_be_sorted, key=itemgetter('name'), reverse=True)
import operator
To sort the list of dictionaries by key='name':
list_of_dicts.sort(key=operator.itemgetter('name'))
To sort the list of dictionaries by key='age':
list_of_dicts.sort(key=operator.itemgetter('age'))
Let's say i have a list from a json like this:
list = [
{
"name": "Player1",
"currency": "10"
},
{
"name": "Player2",
"currency": "15"
},
{
"name": "Player3",
"currency": "7"
}]
How do i sort it based on the amount of currency a player has so that i can get the richest players using simple list indexes? For example if i want the details of the second richest player, i sort the list based on the amount of currency player has and then do a simple list[1] to get their details. How do i achieve this?