Python按数字结尾对字符串进行排序

11

如何最简单地对一个以数字结尾的字符串列表进行排序,其中有一些字符串末尾是三位数字,另一些是四位数字:

>>> list = ['asdf123', 'asdf1234', 'asdf111', 'asdf124']
>>> list.sort()
>>> print list
['asdf111', 'asdf123', 'asdf1234', 'asdf124']

应该把1234放在最后,有没有简单的方法可以做到这一点?


8
请不要将"list"作为变量名。这是一个非常糟糕的做法。 - S.Lott
8个回答

22

有没有简单的方法做到这一点?

是的。

您可以使用natsort模块。

>>> from natsort import natsorted
>>> natsorted(['asdf123', 'asdf1234', 'asdf111', 'asdf124'])
['asdf111', 'asdf123', 'asdf124', 'asdf1234']

完全透明,我是该软件包的作者。


1
这真是救命稻草!简单而高效。 - AleB

7

有没有简单的方法做到这点?

没有。

真正的规则并不清楚。"一些有三位数字,一些有四位数字"并不是非常精确或完整的说明。所有的例子都在数字前面有四个字母。这总是正确的吗?

import re
key_pat = re.compile(r"^(\D+)(\d+)$")
def key(item):
    m = key_pat.match(item)
    return m.group(1), int(m.group(2))

那个 key 函数可能能够满足您的需求。或者它可能过于复杂。或者也许模式实际上是 r"^(.*)(\d{3,4})$",或者规则甚至更加难懂。
>>> data= ['asdf123', 'asdf1234', 'asdf111', 'asdf124']
>>> data.sort( key=key )
>>> data
['asdf111', 'asdf123', 'asdf124', 'asdf1234']

这几乎就是我要告诉提问者的内容。他需要定义一个正则表达式来分离混合输入,解析整数部分,并进行排序。 - marr75
.sort 函数带有函数参数是一个非常有用的技巧,值得掌握。谢谢。 - Hope

2
您可能正在描述的是所谓的自然排序或人类排序。如果您正在使用Python,可以借鉴Ned的实现
自然排序的算法大致如下:
  • 将每个值分成字母“块”和数字“块”
  • 按每个值的第一个块进行排序
    • 如果块是字母的,则按通常方式进行排序
    • 如果块是数字的,则按表示的数值进行排序
  • 取具有相同第一个块的值,并按第二个块对它们进行排序
  • 以此类推

1
问题在于排序是按字母顺序进行的,因为它们是字符串。每个字符序列在移动到下一个字符之前都会进行比较。
>>> 'a1234' < 'a124'  <----- positionally '3' is less than '4' 
True
>>> 

你需要进行数字排序才能得到所需的输出。
>>> x = ['asdf123', 'asdf1234', 'asdf111', 'asdf124']
>>> y = [ int(t[4:]) for t in x]
>>> z = sorted(y)
>>> z
[111, 123, 124, 1234]
>>> l = ['asdf'+str(t) for t in z]
>>> l
['asdf111', 'asdf123', 'asdf124', 'asdf1234']
>>> 

1
l = ['asdf123', 'asdf1234', 'asdf111', 'asdf124']
l.sort(cmp=lambda x,y:cmp(int(x[4:]), int(y[4:]))

2
这在“Rick Rolling”还有趣的时候就是个不错的解决方案(没错,它曾经很有趣)。key自2.4以来就一直存在,而cmp已被弃用并在3.x版中被删除。我真的不知道为什么类似这样的垃圾代码会得到两个赞。它甚至不能比较其余的字符串。 - aaronasterling
他们把cmp给删了?真的吗?我很失望。现在该怎么按照相反的顺序排序字符串呢 - 之后显式地反转吗?这不像是你可以否定一个字符串,以便使用lambda x:-x作为键... - Karl Knechtel
@KarlKnechtel 既然这条评论已经有9年了,你可能现在已经知道了,但是你可以使用sort函数的reverse关键字参数来进行排序时的反转。 - SethMMorton

1

你需要一个关键函数。你愿意在末尾指定3或4个数字,我有一种感觉你想要它们进行数值比较。

sorted(list_, key=lambda s: (s[:-4], int(s[-4:])) if s[-4] in '0123456789' else (s[:-3], int(s[-3:]))) 

没有 lambda 和条件表达式的话,那就……

def key(s):
    if key[-4] in '0123456789':
         return (s[:-4], int(s[-4:]))
    else:
         return (s[:-3], int(s[-3:]))

sorted(list_, key=key)

这只是利用元组按第一个元素,然后按第二个元素排序的事实。因此,由于调用key函数以获取要比较的值,因此现在将像键函数返回的元组一样比较元素。例如,'asdfbad123'将与'asd7890'进行比较,如('asdfbad',123)('asd',7890)进行比较。如果字符串的最后3个字符实际上不是数字,则会引发ValueError,这是完全适当的,因为您传递给它的数据不符合其设计规格。


0

不是自己分割每一行,而是使用 re.findall() 让 Python 为我完成:

import re
import sys

def SortKey(line):
  result = []
  for part in re.findall(r'\D+|\d+', line):
    try:
      result.append(int(part, 10))
    except (TypeError, ValueError) as _:
      result.append(part)
  return result

print ''.join(sorted(sys.stdin.readlines(), key=SortKey)),

0
L.sort(key=lambda s:int(''.join(filter(str.isdigit,s[-4:]))))

aaronasterling 给了我这个想法:L.sort(key=lambda s:int((s[-3:],s[-4:])[s[-4] in '1234567890']))。 - Kabie
这不好,因为'asdf23asdf1234'怎么办?你评论中的解决方案很好但很棘手。但你仍然需要在两个解决方案中包含字符串的其余部分以进行排序。 - aaronasterling
好的,如果你坚持要比较字符串的其余部分,那么你应该将数字作为第一关键字,其余部分作为第二关键字。 - Kabie
这将是非常意外的。如果字符串中的字母先出现,则它们应该先出现在键元组中。因此,'aaron345' 应该排在 'bob123' 之前。 - aaronasterling

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