简体   繁体   中英

Sort list with numbers and letters

I am trying to sort an list which contains numbers and letters:

names = ["5aG", "6bG", "10cG", "J1", ...]

The output should look like this:

['5aG', '5bG', '5aR', '5bR', '6aG', '6bG', '6cG', '6aR', '6bR', '7aG', '7bG', '7aR', '8aG', '8bG', '8aR', '9aG', '9bG', '9aR','10aG', '10bG', '10cG', '10aR', 'J1', 'J2']

The first element of the string is always a number from 5 - 10, then there is a letter from a - c and in the end there is another letter ("G" or "R").

Moreover there are the strings "J1" and "J2". They should be always the last ones ("J1" before "J2").

How can I achieve something like that? I thought about using a lambda function.

So far I hard coded it, but I think there should be a better solution.
This is my hard coded version:

classes = ['5aG', '5bG', '5aR', '5bR', '6aG', '6bG', '6cG', '6aR', '6bR', '7aG', '7bG', '7aR', '8aG', '8bG', '8aR', '9aG', '9bG', '9aR','10aG', '10bG', '10cG', '10aR', 'J1', 'J2']

def s(v):
  """Get index of element in list"""
  try:
    return classes.index(v)
  except ValueError:
return 500

l = ['5bG', '6aG', '6bG', '8aR', '9aG', '9bG', '9aR', '10cG', '10aR', 'J1', 'J2', '5aG', '']
w = sorted( l, key=s)
print(w)

You can use re to extract the front integer, then rely on tuple comparison.

import re

def key(s):
    num, letters = re.match(r'(\d*)(.*)', s).groups()
    return float(num or 'inf'), letters

sorted_names = sorted(names, key=key)

Note how you can rely on float('inf') to have your tokens without prefix digits pushed to the end.

You can you try this:

After scrambling your desired output:

import re
s = ['5aR', '7aR', '10aR', '10cG', '9bG', '8aR', '8bG', '6bR', '5aG', '9aG', 'J1', '6aR', '6aG', '5bR', '7aG', '7bG', '9aR', '5bG', 'J2', '6bG', '10bG', '8aG', '10aG', '6cG']
c, d, *h = sorted(s, key=lambda x:[False if not x[0].isdigit() else int(re.findall('^\d+', x)[0]), x[-1], x[-2]])
sorted_result = [*h, c, d]

Output:

['5aG', '5bG', '5aR', '5bR', '6aG', '6bG', '6cG', '6aR', '6bR', '7aG', '7bG', '7aR', '8aG', '8bG', '8aR', '9aG', '9bG', '9aR', '10aG', '10bG', '10cG', '10aR', 'J1', 'J2']

Here is one way.

lst = ['7aR', '9aG', '7bG', '10cG', '5bG', '6aG', '6bG', '10bG', 'J2', '5aR', '10aG', '9bG', '6aR', '7aG', '10aR', '9aR', '8aR', 'J1', '5bR', '6bR', '5aG', '8bG', '6cG', '8aG']

sorted([i for i in lst if i[0]!='J'], key=lambda x: [int(x[:-2]), x[-1], x[-2]]) + \
sorted(i for i in lst if i[0]=='J')

# ['5aG', '5bG', '5aR', '5bR', '6aG', '6bG', '6cG', '6aR', '6bR', '7aG', '7bG', '7aR', '8aG', '8bG', '8aR', '9aG', '9bG', '9aR', '10aG', '10bG', '10cG', '10aR', 'J1', 'J2']

The easiest way to do this is to use Python's built-in sorting functions. By providing a suitable function as the key argument you can sort things in just about any order you choose.

Internally, when you provide a key function the sort generates a list of two-element tuples. The first element of the tuple is a sort key, the result of applying the key function to the second element, the value from the list. It then sorts those tuples, and returns a list of the second elements. This is known as decorate-sort-undecorate.

Most of your strings are an integer followed by two letters. The remainder, which you wish to appear last, are either "J1" or "J2" . The following should be a suitable key function. I take the precaution of applying the int function to the numbers, to ensure that they sort numerically rather than lexicographically (because '2' > '10' ).

def key_func(s):
    # Ensure J-strings are at the end
    if s.startswith('J'):
        return (1000000, 'J', int(s[1:]))
    else:
        # The rest, split into digits and two characters
        return (int(s[:-2]), s[-2], s[-1])

When tested with a randomised copy of your data the result of

data = ['8aG', '5aR', '6aG', '10aG', '6cG', '8bG', '9aG',
        '5aG', '6bG', '7aR', 'J1', '10cG', '10bG', '10aR',
        '6bR', 'J2', '6aR', '8aR', '7aG', '9aR', '5bR',
        '9bG', '7bG', '5bG']
print(sorted(data, key=key_func))

appears to be correct (line breaks inserted for readability):

['5aG', '5aR', '5bG', '5bR', '6aG', '6aR', '6bG', '6bR',
 '6cG', '7aG', '7aR', '7bG', '8aG', '8aR', '8bG', '9aG',
 '9aR', '9bG', '10aG', '10aR', '10bG', '10cG', 'J1', 'J2']

With custom compound_sort() function:

import re

lst = ['9bG', '9aR', 'J2', '7bG', '7aG', '6bR', 'J1', '6cG', '6aG', '6bG', '5bG', '5aG', '8bG', '5bR', '8aR', '5aR', '10aR', '6aR', '10bG', '10aG', '9aG', '10cG', '7aR', '8aG']
pat = re.compile(r'(\d+)(.*)|(J)(\d+)')

def compound_sort(t):
  t = tuple(filter(None, t))    # filter empty(None) matches
  return (int(t[0]),) + t[1:] if t[0] != 'J' else (float('inf'), t[1])

result = sorted(lst, key=lambda x: compound_sort(pat.search(x).groups()))
print(result)

The output:

['5aG', '5aR', '5bG', '5bR', '6aG', '6aR', '6bG', '6bR', '6cG', '7aG', '7aR', '7bG', '8aG', '8aR', '8bG', '9aG', '9aR', '9bG', '10aG', '10aR', '10bG', '10cG', 'J1', 'J2']

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