使用数字和字母对列表进行排序

3

我正在尝试对一个包含数字和字母的列表进行排序:

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

输出应该是这样的:
['5aG', '5bG', '5aR', '5bR', '6aG', '6bG', '6cG', '6aR', '6bR', '7aG', '7bG', '7aR', '8aG', '8bG', '8aR', '9aG', '9bG', '9aR','10aG', '10bG', '10cG', '10aR', 'J1', 'J2']

字符串的第一个元素始终是5-10之间的数字,紧接着是a-c之间的一个字母,最后还有一个字母("G"或"R")。

此外还有字符串"J1"和"J2"。它们应该永远是最后的两个字符串("J1"在"J2"之前)。

我该如何实现这样的要求呢?我考虑使用lambda函数。

到目前为止,我只是硬编码了一下,但我认为应该有更好的解决方法。
这是我的硬编码版本:

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)
5个回答

2
你可以使用 re 来提取前面的整数,然后依靠 tuple 的比较。
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)

请注意,您可以依靠float('inf')将没有前缀数字的令牌推到末尾。

虽然使用 float(num or 'inf') 看起来很巧妙,但并不特别易读,否则就不需要注释了。可以找到一个可用的 N,以允许使用 int(num) or N。使用 re 是过度设计,因为元素可以仅通过位置确定。 - holdenweb
1
这里的问题是int('')会引发错误。相反,我认为在排序对时使用float('inf')是很自然的。在我看来,使用任意的N缺乏普适性并阻碍可扩展性。 - Olivier Melançon
如果这些数字实际上是浮点数,我可能会对你的技巧感到满意。int('') 的问题就是为什么我选择在自己的尝试中单独处理“J”值的原因。 - holdenweb
1
当然,我理解你的观点。我喜欢我的代码通用且可扩展,在这种情况下可能会有些过度,因为只有两个可能的值不是以int开头。但在这一点上,我认为这真的是个人选择。 - Olivier Melançon
当然,这主要是个人口味问题。自然而然,我的比你的好 :P - holdenweb
+1 个漂亮的通用且可扩展的解决方案,正是这种解决方案使其适用于我的编码项目。谢谢 @OlivierMelançon! - ehusby

1

你可以尝试这样做:

在混淆你所需的输出后:

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]

输出:

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

0

这里是一种方法。

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']

2
也许你的方法有误,请检查你的结果。 - Reck

0

最简单的方法是使用Python内置的排序函数。通过提供适当的函数作为key参数,您可以按照任何顺序对事物进行排序。

在内部,当您提供一个键函数时,排序会生成一个由两个元素组成的元组列表。元组的第一个元素是排序键,是将键函数应用于列表中的第二个元素(值)的结果。然后对这些元组进行排序,并返回第二个元素的列表。这被称为装饰-排序-去除装饰。

大多数字符串都是一个整数后跟两个字母。您希望出现在最后的其余部分要么是"J1",要么是"J2"。以下应该是一个合适的键函数。我采取预防措施,将int函数应用于数字,以确保它们按数字而不是按字典顺序排序(因为'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])

当使用您的数据的随机副本进行测试时,结果为

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))

看起来是正确的(为了易读性插入换行符):

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

0

使用自定义的compound_sort()函数:

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)

输出:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接