Python是否具有用于字符串自然排序的内置函数?

sna*_*ile 249 python sorting python-3.x

使用Python 3.x,我有一个字符串列表,我想对其执行自然的字母排序.

自然排序: Windows中文件的排序顺序.

例如,以下列表是自然排序的(我想要的):

['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Run Code Online (Sandbox Code Playgroud)

这是上面列表的"排序"版本(我有):

['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
Run Code Online (Sandbox Code Playgroud)

我正在寻找一个行为与第一个类似的排序函数.

Set*_*ton 207

在PyPI上有一个名为natsort的第三方库(完全披露,我是包的作者).对于您的情况,您可以执行以下任一操作:

>>> from natsort import natsorted, ns
>>> x = ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
>>> natsorted(x, key=lambda y: y.lower())
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
>>> natsorted(x, alg=ns.IGNORECASE)  # or alg=ns.IC
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Run Code Online (Sandbox Code Playgroud)

您应该注意natsort使用通用算法,因此它应该适用于您抛出的任何输入.如果您想了解更多关于为什么选择库来执行此操作而不是滚动自己的功能的详细信息,请查看natsort文档的" 如何工作"页面,特别是特殊情况下的所有内容!部分.


如果您需要排序键而不是排序功能,请使用以下任一公式.

>>> from natsort import natsort_keygen, ns
>>> l1 = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
>>> l2 = l1[:]
>>> natsort_key1 = natsort_keygen(key=lambda y: y.lower())
>>> l1.sort(key=natsort_key1)
>>> l1
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
>>> natsort_key2 = natsort_keygen(alg=ns.IGNORECASE)
>>> l2.sort(key=natsort_key2)
>>> l2
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Run Code Online (Sandbox Code Playgroud)

  • 我还认为,当数字不在最后时,natsort也会对其进行排序是非常有趣的:就像文件名一样.请随意添加以下示例:http://pastebin.com/9cwCLdEK (5认同)
  • Natsort是一个很棒的库,应该添加到python标准库中!:-) (3认同)

Mar*_*ers 171

试试这个:

import re

def natural_sort(l): 
    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)
Run Code Online (Sandbox Code Playgroud)

输出:

['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Run Code Online (Sandbox Code Playgroud)

看到它在线工作:ideone.

代码改编自:人类排序:自然排序.

  • @jperelli我认为梯形图会改变调用者的原始列表.但很可能调用者想要列表的另一个浅表副本. (11认同)
  • @ user19087:事实上它确实有效,因为`re.split('([0-9] +)','0foo')`返回`['','0','foo']`.因此,字符串将始终在数组中奇数索引上的偶数索引和整数上. (4认同)
  • 只是为了记录,这不能处理所有输入:str/int拆分必须排队,否则你将为输入["foo",0] <[0,"foo"]创建比较["foo0" ","0foo"],引发TypeError. (3认同)
  • 为什么使用`return sorted(l,key)`而不是`l.sort(key)`?它是为了获得任何性能增益还是仅仅为了更加pythonic? (2认同)

Cla*_*diu 93

这是Mark Byer答案的更加pythonic版本:

import re

def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
    return [int(text) if text.isdigit() else text.lower()
            for text in _nsre.split(s)]    
Run Code Online (Sandbox Code Playgroud)

现在,这个功能可以作为在使用它的任何功能,就象是一把钥匙list.sort,sorted,max,等.

作为一个lambda:

lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]
Run Code Online (Sandbox Code Playgroud)

  • re模块自动编译和缓存正则表达式,因此不需要预编译 (9认同)
  • @Cludiu 提到的 X 用法在 Python 2.7 上似乎是 **100**,在 Python 3.4 上是 **512**。还要注意,当达到限制时,缓存将被完全清除(因此它不仅是最旧的缓存被丢弃)。 (3认同)
  • @wim:它缓存最后的 X 次使用,因此技术上可以使用 X+5 个正则表达式,然后一遍又一遍地进行自然排序,此时不会缓存。但从长远来看可能可以忽略不计 (2认同)
  • 当列表元素是“Path”对象时,这不起作用。您可以修改函数以使其正常工作,只需将最后一个“_nsre.split(s)”替换为“_nsre.split(str(s))”即可。与 lambda 相同,将表达式末尾的“s”替换为“str(s)”。 (2认同)

bea*_*ier 19

我写了基于函数http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html这增加还通过在自己的"关键"参数的能力.我需要这个来执行包含更复杂对象(不仅仅是字符串)的自然类型的列表.

import re

def natural_sort(list, key=lambda s:s):
    """
    Sort the list into natural alphanumeric order.
    """
    def get_alphanum_key_func(key):
        convert = lambda text: int(text) if text.isdigit() else text 
        return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))]
    sort_key = get_alphanum_key_func(key)
    list.sort(key=sort_key)
Run Code Online (Sandbox Code Playgroud)

例如:

my_list = [{'name':'b'}, {'name':'10'}, {'name':'a'}, {'name':'1'}, {'name':'9'}]
natural_sort(my_list, key=lambda x: x['name'])
print my_list
[{'name': '1'}, {'name': '9'}, {'name': '10'}, {'name': 'a'}, {'name': 'b'}]
Run Code Online (Sandbox Code Playgroud)


Ser*_*rgO 16

data = ['elm13', 'elm9', 'elm0', 'elm1', 'Elm11', 'Elm2', 'elm10']
Run Code Online (Sandbox Code Playgroud)

我们来分析数据.所有元素的数字容量为2.并且共有3个字母'elm'.

因此,元素的最大长度为5.我们可以增加此值以确保(例如,8).

考虑到这一点,我们有一个单线解决方案:

data.sort(key=lambda x: '{0:0>8}'.format(x).lower())
Run Code Online (Sandbox Code Playgroud)

没有正则表达式和外部库!

print(data)

>>> ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'elm13']
Run Code Online (Sandbox Code Playgroud)

说明:

for elm in data:
    print('{0:0>8}'.format(elm).lower())

>>>
0000elm0
0000elm1
0000elm2
0000elm9
000elm10
000elm11
000elm13
Run Code Online (Sandbox Code Playgroud)


小智 12

鉴于:

data=['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
Run Code Online (Sandbox Code Playgroud)

与SergO的解决方案类似,没有外部库1-liner将是:

data.sort(key=lambda x : int(x[3:]))
Run Code Online (Sandbox Code Playgroud)

要么

sorted_data=sorted(data, key=lambda x : int(x[3:]))
Run Code Online (Sandbox Code Playgroud)

说明:

此解决方案使用sort关键功能来定义将用于排序的函数.因为我们知道每个数据条目都以'elm'开头,所以排序函数将第三个字符后面的字符串部分转换为整数(即​​int(x [3:])).如果数据的数字部分位于不同的位置,则该部分功能必须改变.

干杯

  • 我认为依赖 3 个字符长的前缀不是一个好主意。从长远来看,这是非常不灵活的。 (4认同)

piR*_*red 6

这篇文章的价值

我的观点是提供一个可以普遍应用的非正则表达式解决方案。
我将创建三个函数:

  1. find_first_digit我从@AnuragUniyal借来的。它将找到字符串中第一个数字或非数字的位置。
  2. split_digits这是一个将字符串分成数字和非数字块的生成器。yield当它是数字时,它也将是整数。
  3. natural_key只是包装split_digits成一个tuple. 这就是我们用作sorted, max,的键min

职能

def find_first_digit(s, non=False):
    for i, x in enumerate(s):
        if x.isdigit() ^ non:
            return i
    return -1

def split_digits(s, case=False):
    non = True
    while s:
        i = find_first_digit(s, non)
        if i == 0:
            non = not non
        elif i == -1:
            yield int(s) if s.isdigit() else s if case else s.lower()
            s = ''
        else:
            x, s = s[:i], s[i:]
            yield int(x) if x.isdigit() else x if case else x.lower()

def natural_key(s, *args, **kwargs):
    return tuple(split_digits(s, *args, **kwargs))
Run Code Online (Sandbox Code Playgroud)

我们可以看到它的通用性在于我们可以有多个数字块:

# Note that the key has lower case letters
natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh')

('asl;dkfdfkj:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')
Run Code Online (Sandbox Code Playgroud)

或者保持区分大小写:

natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh', True)

('asl;dkfDFKJ:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')
Run Code Online (Sandbox Code Playgroud)

我们可以看到它按照适当的顺序对 OP 的列表进行了排序

sorted(
    ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'],
    key=natural_key
)

['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Run Code Online (Sandbox Code Playgroud)

但它也可以处理更复杂的列表:

sorted(
    ['f_1', 'e_1', 'a_2', 'g_0', 'd_0_12:2', 'd_0_1_:2'],
    key=natural_key
)

['a_2', 'd_0_1_:2', 'd_0_12:2', 'e_1', 'f_1', 'g_0']
Run Code Online (Sandbox Code Playgroud)

我的正则表达式相当于

def int_maybe(x):
    return int(x) if str(x).isdigit() else x

def split_digits_re(s, case=False):
    parts = re.findall('\d+|\D+', s)
    if not case:
        return map(int_maybe, (x.lower() for x in parts))
    else:
        return map(int_maybe, parts)
    
def natural_key_re(s, *args, **kwargs):
    return tuple(split_digits_re(s, *args, **kwargs))
Run Code Online (Sandbox Code Playgroud)


Jer*_*odG 5

而现在更优雅( pythonic )的东西 - 只是一个触摸

有很多实现,虽然有些已经接近,但没有一个完全捕捉到现代蟒蛇提供的优雅.

  • 使用python(3.5.1)测试
  • 包含一个附加列表,以证明它在数字为中间字符串时有效
  • 但是,没有测试,我假设如果你的列表相当大,那么预先编译正则表达式会更有效率
    • 如果这是一个错误的假设,我相信有人会纠正我

Quicky
from re import compile, split    
dre = compile(r'(\d+)')
mylist.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)])
Run Code Online (Sandbox Code Playgroud) 全码
#!/usr/bin/python3
# coding=utf-8
"""
Natural-Sort Test
"""

from re import compile, split

dre = compile(r'(\d+)')
mylist = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13', 'elm']
mylist2 = ['e0lm', 'e1lm', 'E2lm', 'e9lm', 'e10lm', 'E12lm', 'e13lm', 'elm', 'e01lm']

mylist.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)])
mylist2.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)])

print(mylist)  
  # ['elm', 'elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
print(mylist2)  
  # ['e0lm', 'e1lm', 'e01lm', 'E2lm', 'e9lm', 'e10lm', 'E12lm', 'e13lm', 'elm']
Run Code Online (Sandbox Code Playgroud)

使用时要小心

  • from os.path import split
    • 你需要区分进口

来自的灵感


Wal*_*oss 5

克劳迪乌对马克·拜尔斯答案的改进;-)

import re

def natural_sort_key(s, _re=re.compile(r'(\d+)')):
    return [int(t) if i & 1 else t.lower() for i, t in enumerate(_re.split(s))]

...
my_naturally_sorted_list = sorted(my_list, key=natural_sort_key)
Run Code Online (Sandbox Code Playgroud)

顺便说一句,也许不是每个人都记得函数参数默认值是在def时间评估的


归档时间:

查看次数:

75975 次

最近记录:

6 年,2 月 前