是否有用于字符串自然排序的内置函数?
- 2024-11-18 08:41:00
- admin 原创
- 13
问题描述:
我有一个字符串列表,我想对其进行自然字母排序。
例如,以下列表是自然排序的(我想要的):
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
以下是上述列表的“排序”版本(我使用得到的sorted()
):
['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
我正在寻找一个与第一个行为相似的排序函数。
解决方案 1:
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']
您应该注意到,它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']
2020 年 11 月更新
鉴于一个常见的请求/问题是“如何像 Windows 资源管理器一样排序?”(或您操作系统的文件系统浏览器),从natsort
版本 7.1.0 开始,有一个函数专门os_sorted
用于执行此操作。在 Windows 上,它将按与 Windows 资源管理器相同的顺序排序,而在其他操作系统上,它应该像本地文件系统浏览器一样排序。
>>> from natsort import os_sorted
>>> os_sorted(list_of_paths)
# your paths sorted like your file system browser
对于那些需要排序键的用户,您可以使用os_sort_keygen
(或者os_sort_key
如果您只需要默认值)。
警告- 请在使用之前阅读此功能的 API 文档,以了解其局限性以及如何获得最佳结果。
解决方案 2:
尝试一下:
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)
输出:
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
代码改编自此处:人类排序:自然排序顺序。
解决方案 3:
以下是 Mark Byer 答案的更 Python 版本:
import re
def natural_sort_key(s, _nsre=re.compile(r'(d+)')):
return [int(text) if text.isdigit() else text.lower()
for text in _nsre.split(s)]
现在,此功能可用作任何使用它的函数中的键,如list.sort
、、等。sorted
`max`
作为 lambda:
lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(r'(d+)', s)]
完全可重现的演示代码:
import re
natsort = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(r'(d+)', s)]
L = ["a1", "a10", "a11", "a2", "a22", "a3"]
print(sorted(L, key=natsort))
# ['a1', 'a2', 'a3', 'a10', 'a11', 'a22']
解决方案 4:
data = ['elm13', 'elm9', 'elm0', 'elm1', 'Elm11', 'Elm2', 'elm10']
我们来分析一下数据,所有元素的位数都是2,公共文字部分有3个字母'elm'
。
所以,元素的最大长度是5。我们可以增加这个值以确保(例如,到8)。
考虑到这一点,我们得到了一行解决方案:
data.sort(key=lambda x: '{0:0>8}'.format(x).lower())
无需正则表达式和外部库!
print(data)
>>> ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'elm13']
解释:
for elm in data:
print('{0:0>8}'.format(elm).lower())
>>>
0000elm0
0000elm1
0000elm2
0000elm9
000elm10
000elm11
000elm13
解决方案 5:
我根据http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html编写了一个函数,该函数添加了仍可传入您自己的“key”参数的功能。我需要此功能来对包含更复杂对象(而不仅仅是字符串)的列表进行自然排序。
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)
例如:
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'}]
解决方案 6:
鉴于:
data = ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
类似于 SergO 的解决方案,没有外部库的 1 行代码如下:
data.sort(key=lambda x: int(x[3:]))
或者
sorted_data = sorted(data, key=lambda x: int(x[3:]))
解释:
此解决方案使用sort的关键特性来定义将用于排序的函数。因为我们知道每个数据条目前面都有“elm”,所以排序函数将字符串第 3 个字符后的部分转换为整数(即 int(x[3:]))。如果数据的数值部分位于不同的位置,那么函数的这一部分就必须更改。
解决方案 7:
现在来点更优雅的东西(pythonic) -只需轻轻一触
目前已有许多实现,尽管有些已经接近,但没有一个能够完全捕捉到现代 Python 所提供的优雅。
使用python(3.5.1)测试
包含一个附加列表来演示当数字位于字符串中间时它如何工作
不过,我没有测试,我假设如果你的列表很大,那么预先编译正则表达式会更有效率
+ *如果这是一个错误的假设,我相信有人会纠正我*
快
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)])
全代码
#!/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 文档-如何进行排序
人类排序:自然排序顺序
人工分拣
本文及参考文章的贡献者/评论者
解决方案 8:
这篇文章的价值
我的观点是提供一种可以普遍应用的非正则表达式解决方案。
我将创建三个函数:
find_first_digit
这是我从@AnuragUniyal那里借来的。它将找到字符串中第一个数字或非数字的位置。split_digits
这是一个生成器,它将字符串分解为数字和非数字块。yield
当字符串是数字时,它也会将其分解为整数。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))
我们可以看到,它的通用性在于我们可以有多个数字块:
# Note that the key has lower case letters
natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh')
('asl;dkfdfkj:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')
或者保留区分大小写:
natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh', True)
('asl;dkfDFKJ:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')
我们可以看到它按照适当的顺序对 OP 的列表进行了排序
sorted(
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'],
key=natural_key
)
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
但它也可以处理更复杂的列表:
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']
我的正则表达式等价于
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))
解决方案 9:
Claudiu 对 Mark Byers 答案的改进;-)
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)
顺便说一句,可能不是每个人都记得函数参数默认值是def
在
解决方案 10:
一种选择是将字符串转换为元组,然后使用扩展形式替换数字http://wiki.answers.com/Q/What_does_expanded_form_mean
这样 a90 将变成 ("a",90,0) 并且 a1 将变成 ("a",1)
下面是一些示例代码(由于它从数字中删除前导 0 的方式,所以效率不高)
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)
输出:
('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']
解决方案 11:
根据这里的答案,我编写了一个natural_sorted
行为类似于内置函数的函数sorted
:
# Copyright (C) 2018, Benjamin Drung <bdrung@posteo.de>
#
# 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
解决方案 12:
最有可能functools.cmp_to_key()
与 python 排序的底层实现密切相关。此外,cmp参数是遗留的。现代方法是将输入项转换为支持所需丰富比较操作的对象。
在 CPython 2.x 下,即使尚未实现相应的丰富比较运算符,也可以对不同类型的对象进行排序。在 CPython 3.x 下,不同类型的对象必须明确支持比较。请参阅Python 如何比较字符串和整数?,其中链接到官方文档。大多数答案都依赖于这种隐式排序。切换到 Python 3.x 将需要一种新类型来实现和统一数字和字符串之间的比较。
Python 2.7.12 (default, Sep 29 2016, 13:30:34)
>>> (0,"foo") < ("foo",0)
True
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
不是必需的,而且一开始可能不是一个好主意,但它确实带来了某些便利。
重复排序字符以强制按大小写排序,并交换大小写以强制小写字母优先排序;这是“自然排序”的典型定义。我无法决定分组类型;有些人可能更喜欢以下类型,这也带来了显着的性能优势:
d = lambda s: s.lower()+s.swapcase()
在使用时,比较运算符被设置为,object
因此它们不会被忽略functools.total_ordering
。
import functools
import itertools
@functools.total_ordering
class NaturalStringA(str):
def __repr__(self):
return "{}({})".format\n ( 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__
import functools
import itertools
@functools.total_ordering
class NaturalStringB(str):
def __repr__(self):
return "{}({})".format\n ( 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__
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\n ( 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__
>>> 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 数字比 还多,因此创建这样的排序同样令人生畏。如果您想要区域感知比较,请按照 Python 的排序方法[0-9]
准备字符串。locale.strxfrm
解决方案 13:
我使用的算法padzero_with_lower
定义如下:
import re
def padzero_with_lower(s):
return re.sub(r'd+', lambda m: m.group(0).rjust(10, '0'), s).lower()
该算法发现:
查找并填充任意长度的数字,使其达到足够长的长度,例如 10
然后,它将字符串变成小写
以下是一个用法示例:
print(padzero_with_lower('file1.txt')) # file0000000001.txt
print(padzero_with_lower('file12.txt')) # file0000000012.txt
print(padzero_with_lower('file23.txt')) # file0000000023.txt
print(padzero_with_lower('file123.txt')) # file0000000123.txt
print(padzero_with_lower('file301.txt')) # file0000000301.txt
print(padzero_with_lower('Dir2/file15.txt')) # dir0000000002/file0000000015.txt
print(padzero_with_lower('dir2/file123.txt')) # dir0000000002/file0000000123.txt
print(padzero_with_lower('dir15/file2.txt')) # dir0000000015/file0000000002.txt
print(padzero_with_lower('Dir15/file15.txt')) # dir0000000015/file0000000015.txt
print(padzero_with_lower('elm0')) # elm0000000000
print(padzero_with_lower('elm1')) # elm0000000001
print(padzero_with_lower('Elm2')) # elm0000000002
print(padzero_with_lower('elm9')) # elm0000000009
print(padzero_with_lower('elm10')) # elm0000000010
print(padzero_with_lower('Elm11')) # elm0000000011
print(padzero_with_lower('Elm12')) # elm0000000012
print(padzero_with_lower('elm13')) # elm0000000013
测试完这个函数后,我们现在可以将其用作密钥,即
lis = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
lis.sort(key=padzero_with_lower)
print(lis)
# Output: ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
解决方案 14:
一个紧凑的解决方案,基于将字符串转换为List[Tuple(str, int)]
。
代码
def string_to_pairs(s, pairs=re.compile(r"(D*)(d*)").findall):
return [(text.lower(), int(digits or 0)) for (text, digits) in pairs(s)[:-1]]
示范
sorted(['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'], key=string_to_pairs)
输出:
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
测试
转型
assert string_to_pairs("") == []
assert string_to_pairs("123") == [("", 123)]
assert string_to_pairs("abc") == [("abc", 0)]
assert string_to_pairs("123abc") == [("", 123), ("abc", 0)]
assert string_to_pairs("abc123") == [("abc", 123)]
assert string_to_pairs("123abc456") == [("", 123), ("abc", 456)]
assert string_to_pairs("abc123efg") == [("abc", 123), ("efg", 0)]
排序
# Some extracts from the test suite of the natsort library. Permalink:
# https://github.com/SethMMorton/natsort/blob/e3c32f5638bf3a0e9a23633495269bea0e75d379/tests/test_natsorted.py
sort_data = [
( # same as test_natsorted_can_sort_as_unsigned_ints_which_is_default()
["a50", "a51.", "a50.31", "a-50", "a50.4", "a5.034e1", "a50.300"],
["a5.034e1", "a50", "a50.4", "a50.31", "a50.300", "a51.", "a-50"],
),
( # same as test_natsorted_numbers_in_ascending_order()
["a2", "a5", "a9", "a1", "a4", "a10", "a6"],
["a1", "a2", "a4", "a5", "a6", "a9", "a10"],
),
( # same as test_natsorted_can_sort_as_version_numbers()
["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"],
["1.9.9a", "1.9.9b", "1.10.1", "1.11", "1.11.4"],
),
( # different from test_natsorted_handles_filesystem_paths()
[
"/p/Folder (10)/file.tar.gz",
"/p/Folder (1)/file (1).tar.gz",
"/p/Folder/file.x1.9.tar.gz",
"/p/Folder (1)/file.tar.gz",
"/p/Folder/file.x1.10.tar.gz",
],
[
"/p/Folder (1)/file (1).tar.gz",
"/p/Folder (1)/file.tar.gz",
"/p/Folder (10)/file.tar.gz",
"/p/Folder/file.x1.9.tar.gz",
"/p/Folder/file.x1.10.tar.gz",
],
),
( # same as test_natsorted_path_extensions_heuristic()
[
"Try.Me.Bug - 09 - One.Two.Three.[text].mkv",
"Try.Me.Bug - 07 - One.Two.5.[text].mkv",
"Try.Me.Bug - 08 - One.Two.Three[text].mkv",
],
[
"Try.Me.Bug - 07 - One.Two.5.[text].mkv",
"Try.Me.Bug - 08 - One.Two.Three[text].mkv",
"Try.Me.Bug - 09 - One.Two.Three.[text].mkv",
],
),
( # same as ns.IGNORECASE for test_natsorted_supports_case_handling()
["Apple", "corn", "Corn", "Banana", "apple", "banana"],
["Apple", "apple", "Banana", "banana", "corn", "Corn"],
),
]
for (given, expected) in sort_data:
assert sorted(given, key=string_to_pairs) == expected
奖金
如果您的字符串混合了非 ASCII 文本和数字,您可能有兴趣使用我在其他地方string_to_pairs()
提供的函数进行组合。remove_diacritics()
解决方案 15:
这是一个更先进的解决方案,由 Claudiu 和 Mark Byers 改进:
它使用
casefold()
而不是lower()
匹配字符串您可以传递另一个键 lambda 来选择内部元素(就像使用普通排序函数一样)
当然,它可以与
list.sort
、sorted
、max
等一起使用。
def natural_sort(key=None, _nsre=re.compile('([0-9]+)')):
return lambda x: [int(text) if text.isdigit() else text.casefold()
for text in _nsre.split(key(x) if key else x)]
使用示例:
# Original solution
data.sort(key=natural_sort())
# Select an additional key
image_files.sort(key=natural_sort(lambda x: x.original_filename))
解决方案 16:
上述答案对于所展示的具体示例很有用,但对于更普遍的自然排序问题,缺少几个有用的案例。我刚刚被其中一个案例困扰,因此创建了一个更彻底的解决方案:
def natural_sort_key(string_or_number):
"""
by Scott S. Lawton <scott@ProductArchitect.com> 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 上和 StackOverflow 外)在这里:
http ://productarchitect.com/code/better-natural-sort.py
欢迎反馈。这并不意味着是一个明确的解决方案;只是向前迈出了一步。
解决方案 17:
根据@Mark Byers 的回答,这里有一个接受参数的改编key
,并且更符合 PEP8 标准。
def natsorted(seq, key=None):
def convert(text):
return int(text) if text.isdigit() else text
def alphanum(obj):
if key is not None:
return [convert(c) for c in re.split(r'([0-9]+)', key(obj))]
return [convert(c) for c in re.split(r'([0-9]+)', obj)]
return sorted(seq, key=alphanum)
我也提出了一个要点
解决方案 18:
def sort_naturally(lst: list) -> list:
max_str_len = max([len(s) for s in lst])
return sorted(lst, key=lambda s: s.zfill(max_str_len + 1))
仅供参考,内置函数str.zfill(width)
返回字符串的副本,其中左侧填充有 ASCII数字,以形成长度为宽度0
的字符串。请参阅官方文档了解更多信息:docs.python.org/3/library/stdtypes.html#str.zfill
解决方案 19:
另一个简单的解决方案是使用 (str, int) 对定义一个“自然”排序键:
import re
def get_natural_sort_key(key):
return [('0', int(s)) if s.isdigit() else (s.lower(), 0)
for s in re.findall('[0-9]+|[^0-9]+', key)]
然后使用它:
xs = ['Elm11', 'Elm2', 'elm1', 'Elm12', 'elm9', 'elm0', 'elm13', 'elm10']
sorted_xs = sorted(xs, key=get_natural_sort_key)
结果是:
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
与其他一些解决方案不同,此解决方案将“$”和“#”等字符保留在数字之前,就像默认排序算法一样。
解决方案 20:
让我来谈谈我对这一需求的看法:
from typing import Tuple, Union, Optional, Generator
StrOrInt = Union[str, int]
# On Python 3.6, string concatenation is REALLY fast
# Tested myself, and this fella also tested:
# https://blog.ganssle.io/articles/2019/11/string-concat.html
def griter(s: str) -> Generator[StrOrInt, None, None]:
last_was_digit: Optional[bool] = None
cluster: str = ""
for c in s:
if last_was_digit is None:
last_was_digit = c.isdigit()
cluster += c
continue
if c.isdigit() != last_was_digit:
if last_was_digit:
yield int(cluster)
else:
yield cluster
last_was_digit = c.isdigit()
cluster = ""
cluster += c
if last_was_digit:
yield int(cluster)
else:
yield cluster
return
def grouper(s: str) -> Tuple[StrOrInt, ...]:
return tuple(griter(s))
现在如果我们有这样的列表:
filelist = [
'File3', 'File007', 'File3a', 'File10', 'File11', 'File1', 'File4', 'File5',
'File9', 'File8', 'File8b1', 'File8b2', 'File8b11', 'File6'
]
我们可以简单地使用key=
kwarg 进行自然排序:
>>> sorted(filelist, key=grouper)
['File1', 'File3', 'File3a', 'File4', 'File5', 'File6', 'File007', 'File8',
'File8b1', 'File8b2', 'File8b11', 'File9', 'File10', 'File11']
这里的缺点当然是,就像现在一样,该函数会将大写字母排序在小写字母之前。
我将不区分大小写的分组器的实现留给读者:-)
解决方案 21:
仅供参考,这里是 Mark Byers 简单解决方案的另一种变体,类似于 Walter Tross 建议的解决方案,它避免了调用isdigit()
。这不仅使其更快,而且还避免了由于将更多的unicode字符视为数字而可能出现的问题,isdigit()
而不是正则表达式d+
。
import re
from itertools import cycle
_re_digits = re.compile(r"(d+)")
def natural_comparison_key(key):
return tuple(
int(part) if is_digit else part
for part, is_digit in zip(_re_digits.split(key), cycle((False, True)))
)
解决方案 22:
这是 Mark Byers 答案的另一个版本。此版本演示了如何传递属性名称,该名称将用于评估列表中的对象。
def natural_sort(l, attrib):
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.__dict__[attrib])]
return sorted(l, key=alphanum_key)
results = natural_sort(albums, 'albumid')
其中,albums
是 Album 实例的列表,并且albumid
是名义上包含数字的字符串属性。
解决方案 23:
有些答案已经使用过re.split
,但忽略了数字位于奇数索引处并再次str.isdigit
用于识别它们。我们可以利用这些知识并直接应用于奇数索引:int
import re, random
def key(s):
a = re.split(r'(d+)', s.casefold())
a[1::2] = map(int, a[1::2])
return a
expect = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
shuffled = expect.copy()
random.shuffle(shuffled)
print(sorted(shuffled, key=key) == expect)
打印True
。(在线尝试!)
解决方案 24:
我建议您简单地使用key
关键字参数sorted
来实现您想要的列表
例如:
to_order= [e2,E1,e5,E4,e3]
ordered= sorted(to_order, key= lambda x: x.lower())
# ordered should be [E1,e2,e3,E4,e5]
解决方案 25:
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中一次读取一个字母的字符串
解决方案 26:
>>> import re
>>> sorted(lst, key=lambda x: int(re.findall(r'd+$', x)[0]))
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件