简体   繁体   中英

Custom sort a list in python with mixed letters and numbers

I have a list containing 6 strings:

s1 = 'foo[2]_bar'
s2 = 'foo[2]_ant'
s3 = 'foo[1]_bar'
s4 = 'foo[1]_ant'
s5 = 'foo[10]_bar'
s6 = 'foo[10]_ant'
str_arr = [s1, s2, s3, s4, s5, s6]

If I use str_arr.sort() the order comes out like this:

foo[10]_ant
foo[10]_bar
foo[1]_ant
foo[1]_bar
foo[2]_ant
foo[2]_bar

However, I need the order to take more precedence of alphabetical sorting, meaning "ant" should come before "bar" in all cases, but also sub-sorting for numerical order.

So I need the output to be like this but I don't know how to accomplish this:

foo[1]_ant
foo[2]_ant
foo[10]_ant
foo[1]_bar
foo[2]_bar
foo[10]_bar

I'm not sure if there's some sort of filter or lambda function I can use but I can barely understand a lot of lambda functions I see on here.

Use a lambda with multiple conditions. The conditions are prioritized from left-to-right:

Code:

s1 = 'foo[2]_bar'
s2 = 'foo[2]_ant'
s3 = 'foo[1]_bar'
s4 = 'foo[1]_ant'
s5 = 'foo[10]_bar'
s6 = 'foo[10]_ant'
str_arr = [s1, s2, s3, s4, s5, s6]

str_arr.sort(key=lambda x: (x[x.find("_")+1], int(x[x.find("[")+1:x.find("]")])))
print(str_arr)

Output:

['foo[1]_ant', 'foo[2]_ant', 'foo[10]_ant', 'foo[1]_bar', 'foo[2]_bar', 'foo[10]_bar']

How it works:

(For every entry x )

From left-to-right we have the conditions:

  1. x[x.find("_")+1:] This is the first sorting condition. It will sort based on the word that comes after the underscore. Here, we slice the string by finding the underscore, and returning the portion of the string that occurs after it.

  2. int(x[x.find("[")+1:x.find("]")]) This is the second sorting condition. It will sort based on the int between the [] in the string. It slices the string so we are left with only that number, then it converts that number to an int , and compares the int to the integers in the other strings.

As stated above, using a lambda with multiple conditions means it will prioritize the sorting conditions from left-to-right. Here, we prioritize sorting by the last word (so ant always comes before bar ), and then sorting by the integer (so 1...ant comes before 2...ant and so on.

Another solution, using re :

import re

s1 = "foo[2]_bar"
s2 = "foo[2]_ant"
s3 = "foo[1]_bar"
s4 = "foo[1]_ant"
s5 = "foo[10]_bar"
s6 = "foo[10]_ant"
str_arr = [s1, s2, s3, s4, s5, s6]

pat = re.compile(r"\[(\d+)\]_(.*)")

print(sorted(str_arr, key=lambda k: ((m := pat.search(k))[2], int(m[1]))))

Prints:

[
    "foo[1]_ant",
    "foo[2]_ant",
    "foo[10]_ant",
    "foo[1]_bar",
    "foo[2]_bar",
    "foo[10]_bar",
]

make a custom sort function and then pass it as a key

You can return multiple objects from your custom sort function and priority will be given on the basis of order of the returning.

s1 = 'foo[2]_bar'
s2 = 'foo[2]_ant'
s3 = 'foo[1]_bar'
s4 = 'foo[1]_ant'
s5 = 'foo[10]_bar'
s6 = 'foo[10]_ant'
str_arr = [s1, s2, s3, s4, s5, s6]

def custom_sort(x):
    last_str = x.split("_")[-1]
    num = x[x.index("[")+1: x.index("]")]
    return last_str, int(num)

for i in (sorted(str_arr, key=custom_sort)):
    print(i)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM