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

Does Python have a built in function for string natural sort?

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

自然排序:窗口中文件的排序顺序。

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

1
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

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

1
['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']

我在找一个类似于第一个的排序函数。


Pypi上有一个名为natsort的第三方库(完全公开,我是包的作者)。对于您的案例,您可以执行以下任一操作:

1
2
3
4
5
6
>>> 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']

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

如果需要排序键而不是排序函数,请使用以下公式之一。

1
2
3
4
5
6
7
8
9
10
11
>>> 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']


试试这个:

1
2
3
4
5
6
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)

输出:

1
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

看它在网上工作:Ideone。

从这里改编的代码:人类排序:自然排序顺序。


下面是马克·拜尔答案的一个更为Python式的版本:

1
2
3
4
5
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)]

现在,这个函数可以用作任何使用它的函数的键,如list.sortsortedmax等。

作为一个lambda:

1
lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]


我写了一个基于http://www.codingsurror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html的函数,它增加了传递自己的"key"参数的能力。为了执行包含更复杂对象(不仅仅是字符串)的自然列表,我需要这样做。

1
2
3
4
5
6
7
8
9
10
11
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)

例如:

1
2
3
4
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'}]


1
data = ['elm13', 'elm9', 'elm0', 'elm1', 'Elm11', 'Elm2', 'elm10']

让我们分析一下数据。所有元素的位数容量为2。共有3个字母,文字部分为'elm'

所以,元素的最大长度是5。我们可以增加这个值以确保(例如,到8)。

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

1
data.sort(key=lambda x: '{0:0>8}'.format(x).lower())

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

1
2
3
print(data)

>>> ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'elm13']

说明:

1
2
3
4
5
6
7
8
9
10
11
for elm in data:
    print('{0:0>8}'.format(elm).lower())

>>>
0000elm0
0000elm1
0000elm2
0000elm9
000elm10
000elm11
000elm13


鉴于:

1
data=['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']

与Sergo的解决方案类似,没有外部库的1行程序将是:

1
data.sort(key=lambda x : int(x[3:]))

1
sorted_data=sorted(data, key=lambda x : int(x[3:]))

说明:

此解决方案使用排序的关键特性来定义将用于排序的函数。因为我们知道每个数据条目前面都有"elm",所以排序函数将字符串中第3个字符后的部分(即int(x[3:]))转换为整数。如果数据的数字部分位于不同的位置,那么函数的这一部分就必须更改。

干杯


现在,为了更优雅的东西(Python)——只需轻轻一点

外面有很多实现,虽然有些实现已经接近尾声,但没有一个完全捕捉到现代Python所提供的优雅。

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

快的

1
2
3
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)])

全码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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']

使用时注意

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

灵感来自

  • python文档-排序方法
  • 人类分类:自然分类顺序
  • 人类分类
  • 投稿人/评论员


一种选择是将字符串转换为元组,并使用扩展格式http://wiki.answers.com/q/what_expanded_form_means替换数字。

这样A90会变成("A",90,0),A1会变成("A",1)

下面是一些示例代码(由于它从数字中删除前导0的方式,因此效率不是很高)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
alist=["something1",
   "something12",
   "something17",
   "something2",
   "something25and_then_33",
   "something25and_then_34",
   "something29",
   "beta1.1",
   "beta2.3.0",
   "beta2.33.1",
   "a001",
   "a2",
   "z002",
   "z1"]

def key(k):
    nums=set(list("0123456789"))
        chars=set(list(k))
    chars=chars-nums
    for i in range(len(k)):
        for c in chars:
            k=k.replace(c+"0",c)
    l=list(k)
    base=10
    j=0
    for i in range(len(l)-1,-1,-1):
        try:
            l[i]=int(l[i])*base**j
            j+=1
        except:
            j=0
    l=tuple(l)
    print l
    return l

print sorted(alist,key=key)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 1)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 10, 2)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 10, 7)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 2)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 5, 'a', 'n', 'd', '_', 't', 'h', 'e', 'n', '_', 30, 3)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 5, 'a', 'n', 'd', '_', 't', 'h', 'e', 'n', '_', 30, 4)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 9)
('b', 'e', 't', 'a', 1, '.', 1)
('b', 'e', 't', 'a', 2, '.', 3, '.')
('b', 'e', 't', 'a', 2, '.', 30, 3, '.', 1)
('a', 1)
('a', 2)
('z', 2)
('z', 1)
['a001', 'a2', 'beta1.1', 'beta2.3.0', 'beta2.33.1', 'something1', 'something2', 'something12', 'something17', 'something25and_then_33', 'something25and_then_34', 'something29', 'z1', 'z002']


这篇文章的价值

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

  • 我从@anuraguniyal借来的find_first_digit。它将找到字符串中第一个数字或非数字的位置。
  • split_digits是一个生成器,它将字符串分成数字和非数字块。当它是一个数字时,它也将使cx1〔2〕整数。
  • natural_key只是把split_digits包进tuple中。这是我们用作sortedmaxmin的密钥。
  • 功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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))

    我们可以看到,通常我们可以有多个数字块:

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

    ('asl;dkfdfkj:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')

    或视情况而定:

    1
    2
    3
    natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh', True)

    ('asl;dkfDFKJ:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')

    我们可以看到它按适当的顺序对操作列表进行排序。

    1
    2
    3
    4
    5
    6
    sorted(
        ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'],
        key=natural_key
    )

    ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

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

    1
    2
    3
    4
    5
    6
    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']

    我的雷杰克斯相当于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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))

    基于这里的答案,我编写了一个natural_sorted函数,它的行为类似于内置函数sorted

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    # Copyright (C) 2018, Benjamin Drung <[email protected]>
    #
    # Permission to use, copy, modify, and/or distribute this software for any
    # purpose with or without fee is hereby granted, provided that the above
    # copyright notice and this permission notice appear in all copies.
    #
    # THE SOFTWARE IS PROVIDED"AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

    import re

    def natural_sorted(iterable, key=None, reverse=False):
       """Return a new naturally sorted list from the items in *iterable*.

        The returned list is in natural sort order. The string is ordered
        lexicographically (using the Unicode code point number to order individual
        characters), except that multi-digit numbers are ordered as a single
        character.

        Has two optional arguments which must be specified as keyword arguments.

        *key* specifies a function of one argument that is used to extract a
        comparison key from each list element: ``key=str.lower``.  The default value
        is ``None`` (compare the elements directly).

        *reverse* is a boolean value.  If set to ``True``, then the list elements are
        sorted as if each comparison were reversed.

        The :func:`natural_sorted` function is guaranteed to be stable. A sort is
        stable if it guarantees not to change the relative order of elements that
        compare equal --- this is helpful for sorting in multiple passes (for
        example, sort by department, then by salary grade).
       """

        prog = re.compile(r"(\d+)")

        def alphanum_key(element):
           """Split given key in list of strings and digits"""
            return [int(c) if c.isdigit() else c for c in prog.split(key(element)
                    if key else element)]

        return sorted(iterable, key=alphanum_key, reverse=reverse)

    源代码也可以在我的Github代码段存储库中找到:https://github.com/bdrung/snippets/blob/master/natural_sorted.py


    最有可能的情况是,functools.cmp_to_key()与Python类的底层实现密切相关。此外,cmp参数是遗留的。现代方法是将输入项转换为支持所需的丰富比较操作的对象。

    在cpython 2.x下,即使没有实现各自的富比较运算符,也可以对不同类型的对象进行排序。在cpython 3.x下,不同类型的对象必须显式支持比较。看看python如何比较string和int?链接到官方文件。大多数答案都依赖于这种隐含的顺序。切换到python 3.x需要一个新的类型来实现和统一数字和字符串之间的比较。

    1
    2
    3
    Python 2.7.12 (default, Sep 29 2016, 13:30:34)
    >>> (0,"foo") < ("foo",0)
    True
    1
    2
    3
    4
    5
    Python 3.5.2 (default, Oct 14 2016, 12:54:53)
    >>> (0,"foo") < ("foo",0)
    Traceback (most recent call last):
      File"<stdin>", line 1, in <module>
      TypeError: unorderable types: int() < str()

    有三种不同的方法。第一种使用嵌套类来利用Python的Iterable比较算法。第二个将此嵌套展开到单个类中。第三种方法是放弃str的子类化,将重点放在性能上。所有这些都是定时的;第二个快一倍,第三个快六倍。不需要对str进行子类化,一开始可能是个坏主意,但它确实带来了一些便利。

    排序字符被复制以强制按大小写排序,大小写交换以强制小写字母优先排序;这是"自然排序"的典型定义。我无法决定分组的类型;有些人可能更喜欢以下类型,这也带来了显著的性能优势:

    1
    d = lambda s: s.lower()+s.swapcase()

    如果使用比较运算符,则将其设置为object的比较运算符,这样functools.total_ordering就不会忽略它们。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    import functools
    import itertools


    @functools.total_ordering
    class NaturalStringA(str):
        def __repr__(self):
            return"{}({})".format\
                ( type(self).__name__
                , super().__repr__()
                )
        d = lambda c, s: [ c.NaturalStringPart("".join(v))
                            for k,v in
                           itertools.groupby(s, c.isdigit)
                         ]
        d = classmethod(d)
        @functools.total_ordering
        class NaturalStringPart(str):
            d = lambda s:"".join(c.lower()+c.swapcase() for c in s)
            d = staticmethod(d)
            def __lt__(self, other):
                if not isinstance(self, type(other)):
                    return NotImplemented
                try:
                    return int(self) < int(other)
                except ValueError:
                    if self.isdigit():
                        return True
                    elif other.isdigit():
                        return False
                    else:
                        return self.d(self) < self.d(other)
            def __eq__(self, other):
                if not isinstance(self, type(other)):
                    return NotImplemented
                try:
                    return int(self) == int(other)
                except ValueError:
                    if self.isdigit() or other.isdigit():
                        return False
                    else:
                        return self.d(self) == self.d(other)
            __le__ = object.__le__
            __ne__ = object.__ne__
            __gt__ = object.__gt__
            __ge__ = object.__ge__
        def __lt__(self, other):
            return self.d(self) < self.d(other)
        def __eq__(self, other):
            return self.d(self) == self.d(other)
        __le__ = object.__le__
        __ne__ = object.__ne__
        __gt__ = object.__gt__
        __ge__ = object.__ge__
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    import functools
    import itertools


    @functools.total_ordering
    class NaturalStringB(str):
        def __repr__(self):
            return"{}({})".format\
                ( type(self).__name__
                , super().__repr__()
                )
        d = lambda s:"".join(c.lower()+c.swapcase() for c in s)
        d = staticmethod(d)
        def __lt__(self, other):
            if not isinstance(self, type(other)):
                return NotImplemented
            groups = map(lambda i: itertools.groupby(i, type(self).isdigit), (self, other))
            zipped = itertools.zip_longest(*groups)
            for s,o in zipped:
                if s is None:
                    return True
                if o is None:
                    return False
                s_k, s_v = s[0],"".join(s[1])
                o_k, o_v = o[0],"".join(o[1])
                if s_k and o_k:
                    s_v, o_v = int(s_v), int(o_v)
                    if s_v == o_v:
                        continue
                    return s_v < o_v
                elif s_k:
                    return True
                elif o_k:
                    return False
                else:
                    s_v, o_v = self.d(s_v), self.d(o_v)
                    if s_v == o_v:
                        continue
                    return s_v < o_v
            return False
        def __eq__(self, other):
            if not isinstance(self, type(other)):
                return NotImplemented
            groups = map(lambda i: itertools.groupby(i, type(self).isdigit), (self, other))
            zipped = itertools.zip_longest(*groups)
            for s,o in zipped:
                if s is None or o is None:
                    return False
                s_k, s_v = s[0],"".join(s[1])
                o_k, o_v = o[0],"".join(o[1])
                if s_k and o_k:
                    s_v, o_v = int(s_v), int(o_v)
                    if s_v == o_v:
                        continue
                    return False
                elif s_k or o_k:
                    return False
                else:
                    s_v, o_v = self.d(s_v), self.d(o_v)
                    if s_v == o_v:
                        continue
                    return False
            return True
        __le__ = object.__le__
        __ne__ = object.__ne__
        __gt__ = object.__gt__
        __ge__ = object.__ge__
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    import functools
    import itertools
    import enum


    class OrderingType(enum.Enum):
        PerWordSwapCase         = lambda s: s.lower()+s.swapcase()
        PerCharacterSwapCase    = lambda s:"".join(c.lower()+c.swapcase() for c in s)


    class NaturalOrdering:
        @classmethod
        def by(cls, ordering):
            def wrapper(string):
                return cls(string, ordering)
            return wrapper
        def __init__(self, string, ordering=OrderingType.PerCharacterSwapCase):
            self.string = string
            self.groups = [ (k,int("".join(v)))
                                if k else
                            (k,ordering("".join(v)))
                                for k,v in
                            itertools.groupby(string, str.isdigit)
                          ]
        def __repr__(self):
            return"{}({})".format\
                ( type(self).__name__
                , self.string
                )
        def __lesser(self, other, default):
            if not isinstance(self, type(other)):
                return NotImplemented
            for s,o in itertools.zip_longest(self.groups, other.groups):
                if s is None:
                    return True
                if o is None:
                    return False
                s_k, s_v = s
                o_k, o_v = o
                if s_k and o_k:
                    if s_v == o_v:
                        continue
                    return s_v < o_v
                elif s_k:
                    return True
                elif o_k:
                    return False
                else:
                    if s_v == o_v:
                        continue
                    return s_v < o_v
            return default
        def __lt__(self, other):
            return self.__lesser(other, default=False)
        def __le__(self, other):
            return self.__lesser(other, default=True)
        def __eq__(self, other):
            if not isinstance(self, type(other)):
                return NotImplemented
            for s,o in itertools.zip_longest(self.groups, other.groups):
                if s is None or o is None:
                    return False
                s_k, s_v = s
                o_k, o_v = o
                if s_k and o_k:
                    if s_v == o_v:
                        continue
                    return False
                elif s_k or o_k:
                    return False
                else:
                    if s_v == o_v:
                        continue
                    return False
            return True
        # functools.total_ordering doesn't create single-call wrappers if both
        # __le__ and __lt__ exist, so do it manually.
        def __gt__(self, other):
            op_result = self.__le__(other)
            if op_result is NotImplemented:
                return op_result
            return not op_result
        def __ge__(self, other):
            op_result = self.__lt__(other)
            if op_result is NotImplemented:
                return op_result
            return not op_result
        # __ne__ is the only implied ordering relationship, it automatically
        # delegates to __eq__
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> import natsort
    >>> import timeit
    >>> l1 = ['Apple', 'corn', 'apPlE', 'arbour', 'Corn', 'Banana', 'apple', 'banana']
    >>> l2 = list(map(str, range(30)))
    >>> l3 = ["{} {}".format(x,y) for x in l1 for y in l2]
    >>> print(timeit.timeit('sorted(l3+["0"], key=NaturalStringA)', number=10000, globals=globals()))
    362.4729259099986
    >>> print(timeit.timeit('sorted(l3+["0"], key=NaturalStringB)', number=10000, globals=globals()))
    189.7340817489967
    >>> print(timeit.timeit('sorted(l3+["0"], key=NaturalOrdering.by(OrderingType.PerCharacterSwapCase))', number=10000, globals=globals()))
    69.34636392899847
    >>> print(timeit.timeit('natsort.natsorted(l3+["0"], alg=natsort.ns.GROUPLETTERS | natsort.ns.LOWERCASEFIRST)', number=10000, globals=globals()))
    98.2531585780016

    自然排序既相当复杂,又模糊地定义为一个问题。不要忘记提前运行unicodedata.normalize(...),考虑使用str.casefold(),而不是str.lower()。可能有一些细微的编码问题我没有考虑过。所以我暂时推荐Natsort图书馆。我快速浏览了一下Github存储库;代码维护非常出色。

    我看到的所有算法都依赖于一些技巧,比如复制和降低字符,以及交换大小写。虽然这会使运行时间加倍,但另一种方法需要对输入字符集进行完全自然的排序。我不认为这是Unicode规范的一部分,因为Unicode数字比[0-9]多,创建这样的排序同样令人望而生畏。如果您想要了解区域设置的比较,根据python的排序方式,使用locale.strxfrm准备字符串。


    上面的答案对于所展示的具体例子是很好的,但是对于更一般的自然类问题,遗漏了几个有用的例子。我刚从这些案例中得到一点教训,所以创造了一个更彻底的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    def natural_sort_key(string_or_number):
       """
        by Scott S. Lawton <[email protected]> 2014-12-11; public domain and/or CC0 license

        handles cases where simple 'int' approach fails, e.g.
            ['0.501', '0.55'] floating point with different number of significant digits
            [0.01, 0.1, 1]    already numeric so regex and other string functions won't work (and aren't required)
            ['elm1', 'Elm2']  ASCII vs. letters (not case sensitive)
       """


        def try_float(astring):
            try:
                return float(astring)
            except:
                return astring

        if isinstance(string_or_number, basestring):
            string_or_number = string_or_number.lower()

            if len(re.findall('[.]\d', string_or_number)) <= 1:
                # assume a floating point value, e.g. to correctly sort ['0.501', '0.55']
                # '.' for decimal is locale-specific, e.g. correct for the Anglosphere and Asia but not continental Europe
                return [try_float(s) for s in re.split(r'([\d.]+)', string_or_number)]
            else:
                # assume distinct fields, e.g. IP address, phone number with '.', etc.
                # caveat: might want to first split by whitespace
                # TBD: for unicode, replace isdigit with isdecimal
                return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_or_number)]
        else:
            # consider: add code to recurse for lists/tuples and perhaps other iterables
            return string_or_number

    测试代码和几个链接(stackoverflow的on和off)如下:http://productarchitect.com/code/better-natural-sort.py

    欢迎反馈。这不是一个确定的解决方案,只是向前迈出了一步。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    a = ['H1', 'H100', 'H10', 'H3', 'H2', 'H6', 'H11', 'H50', 'H5', 'H99', 'H8']
    b = ''
    c = []

    def bubble(bad_list):#bubble sort method
            length = len(bad_list) - 1
            sorted = False

            while not sorted:
                    sorted = True
                    for i in range(length):
                            if bad_list[i] > bad_list[i+1]:
                                    sorted = False
                                    bad_list[i], bad_list[i+1] = bad_list[i+1], bad_list[i] #sort the integer list
                                    a[i], a[i+1] = a[i+1], a[i] #sort the main list based on the integer list index value

    for a_string in a: #extract the number in the string character by character
            for letter in a_string:
                    if letter.isdigit():
                            #print letter
                            b += letter
            c.append(b)
            b = ''

    print 'Before sorting....'
    print a
    c = map(int, c) #converting string list into number list
    print c
    bubble(c)

    print 'After sorting....'
    print c
    print a

    确认:

    气泡排序作业

    如何在python中一次读取一个字母的字符串


    我建议您使用sortedkey关键字参数来实现您想要的列表。例如:

    1
    2
    3
    to_order= [e2,E1,e5,E4,e3]
    ordered= sorted(to_order, key= lambda x: x.lower())
        # ordered should be [E1,e2,e3,E4,e5]

    1
    2
    3
    >>> import re
    >>> sorted(lst, key=lambda x: int(re.findall(r'\d+$', x)[0]))
    ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']