如何在Python中对字母数字集进行排序

85

我有一个集合

set(['booklet', '4 sheets', '48 sheets', '12 sheets'])

排序后,我希望它看起来像这样:

4 sheets,
12 sheets,
48 sheets,
booklet

请问有什么想法吗?

11个回答

147

Jeff Atwood讨论了自然排序,并给出了一种在Python中实现它的方法示例。下面是我对此的变体:

import re 

def sorted_nicely( l ): 
    """ Sort the given iterable in the way that humans expect.""" 
    convert = lambda text: int(text) if text.isdigit() else text 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

使用方法如下:

s = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])
for x in sorted_nicely(s):
    print(x)

输出:

4 sheets
12 sheets
48 sheets
booklet

这种方法的一个优点是它不仅适用于字符串由空格分隔的情况。它也适用于其他分隔符,例如版本号中的句点(例如 1.9.1 在 1.10.0 之前)。


嗨,Jeff, 非常感谢。那正是我在寻找的。祝你好运。 - mmrs151
2
能否根据元组中的第一个值修改此代码,以适用于元组列表?例如: [('b', 0), ('0', 1), ('a', 2)] 排序后为 [('0', 1), ('a', 2), ('b', 0)] - paragbaxi
3
此函数区分大小写,大写字符串会优先匹配。若要修复此问题,请在re.splitkey中添加.lower() - zamber
@paragbaxi 在 alphanum_key lambda 函数中,在 key 后面添加 [0]lambda key: [ convert(c) for c in re.split('([0-9]+)', key[0]) ] - Justin Lillico

63

简短明了:

sorted(data, key=lambda item: (int(item.partition(' ')[0])
                               if item[0].isdigit() else float('inf'), item))

这个版本:

  • 适用于Python 2和Python 3,因为:
    • 它不假定您比较字符串和整数(在Python 3中不起作用)
    • 它不使用sortedcmp参数(在Python 3中不存在)
  • 如果数量相等,则将按字符串部分排序

如果您希望输出与示例中描述的完全相同,则:

data = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])
r = sorted(data, key=lambda item: (int(item.partition(' ')[0])
                                   if item[0].isdigit() else float('inf'), item))
print ',\n'.join(r)

卡在“4a sheets”上,但谁在乎呢?要解决这个问题,你需要一个真正的函数而不是一个lambda。 - Jean-François Fabre
1
这可能适用于这个简单的例子,但不适用于像 ["1. bla", "2. blub"] 这样的列表。可能应该使用正则表达式进行拆分,并在之后按第二部分排序,这样 ["1 bcd", "2 abc", "1 xyz"] 才能正确输出。 - FrankyBoy
很遗憾,@FrankyBoy是正确的,这种方法无法对版本号进行字母数字排序;例如,v1.0.1、v3.5.3、v3.2.4。 - Mdev

25

你应该查看第三方库natsort。它的算法很通用,所以适用于大多数输入。

>>> import natsort
>>> your_list = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])
>>> print ',\n'.join(natsort.natsorted(your_list))
4 sheets,
12 sheets,
48 sheets,
booklet

11

一个简单的方法是将字符串划分为数字部分和非数字部分,并使用Python元组排序规则对字符串进行排序。

import re
tokenize = re.compile(r'(\d+)|(\D+)').findall
def natural_sortkey(string):          
    return tuple(int(num) if num else alpha for num, alpha in tokenize(string))

sorted(my_set, key=natural_sortkey)

8

有人建议我在这里重新发布这个答案,因为它也可以很好地解决这个问题。

from itertools import groupby
def keyfunc(s):
    return [int(''.join(g)) if k else ''.join(g) for k, g in groupby(s, str.isdigit)]

sorted(my_list, key=keyfunc)

演示:

>>> my_set = {'booklet', '4 sheets', '48 sheets', '12 sheets'}
>>> sorted(my_set, key=keyfunc)
['4 sheets', '12 sheets', '48 sheets', 'booklet']

对于Python3,需要稍微修改一下(此版本在Python2中也可以正常工作)

def keyfunc(s):
    return [int(''.join(g)) if k else ''.join(g) for k, g in groupby('\0'+s, str.isdigit)]

4

这是一个通用的答案,可以对任何位置上包含数字的字符串数组进行排序。支持Python 2和3。

def alphaNumOrder(string):
   """ Returns all numbers on 5 digits to let sort the string with numeric order.
   Ex: alphaNumOrder("a6b12.125")  ==> "a00006b00012.00125"
   """
   return ''.join([format(int(x), '05d') if x.isdigit()
                   else x for x in re.split(r'(\d+)', string)])

示例:

s = ['a10b20','a10b1','a3','b1b1','a06b03','a6b2','a6b2c10','a6b2c5']
s.sort(key=alphaNumOrder)
s ===> ['a3', 'a6b2', 'a6b2c5', 'a6b2c10', 'a06b03', 'a10b1', 'a10b20', 'b1b1']

答案的一部分来自此处


2
>>> a = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])
>>> def ke(s):
    i, sp, _ = s.partition(' ')
    if i.isnumeric():
        return int(i)
    return float('inf')

>>> sorted(a, key=ke)
['4 sheets', '12 sheets', '48 sheets', 'booklet']

1

基于SilentGhost的回答:

In [4]: a = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])

In [5]: def f(x):
   ...:     num = x.split(None, 1)[0]
   ...:     if num.isdigit():
   ...:         return int(num)
   ...:     return x
   ...: 

In [6]: sorted(a, key=f)
Out[6]: ['4 sheets', '12 sheets', '48 sheets', 'booklet']

0

集合本质上是无序的。您需要创建一个包含相同内容并进行排序的列表。


5
不正确——内置函数sorted()可以接受任何序列并返回一个已排序的列表。 - PaulMcG
5
所以,与其创建一个列表再将其排序,你可以直接使用内置函数创建一个已排序的列表... 是的,我完全错了。 - Rakis
实现了SortedSets(而不是HashSets)的集合本质上是有序的 - axwell

0
b = set(['booklet', '10-b40', 'z94 boots', '4 sheets', '48 sheets',
         '12 sheets', '1 thing', '4a sheets', '4b sheets', '2temptations'])

numList = sorted([x for x in b if x.split(' ')[0].isdigit()],
                 key=lambda x: int(x.split(' ')[0]))

alphaList = sorted([x for x in b if not x.split(' ')[0].isdigit()])

sortedList = numList + alphaList

print(sortedList)

Out: ['1 thing',
      '4 sheets',
      '12 sheets',
      '48 sheets',
      '10-b40',
      '2temptations',
      '4a sheets',
      '4b sheets',
      'booklet',
      'z94 boots']

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